diff --git a/z2/Dockerfile b/z2/Dockerfile
new file mode 100644
index 0000000..e689bca
--- /dev/null
+++ b/z2/Dockerfile
@@ -0,0 +1,11 @@
+FROM python:3.9-slim
+
+WORKDIR /app
+
+COPY . /app/
+
+RUN pip install flask mysql-connector-python
+
+EXPOSE 5000
+
+CMD ["python", "app.py"]
\ No newline at end of file
diff --git a/z2/Dockerfile.mysql b/z2/Dockerfile.mysql
new file mode 100644
index 0000000..ec5f9ca
--- /dev/null
+++ b/z2/Dockerfile.mysql
@@ -0,0 +1,7 @@
+FROM mysql:8.0
+
+ENV MYSQL_ALLOW_EMPTY_PASSWORD=true
+
+COPY sensors_db.sql /docker-entrypoint-initdb.d/
+
+EXPOSE 3306
\ No newline at end of file
diff --git a/z2/README.md b/z2/README.md
new file mode 100644
index 0000000..f7797a7
--- /dev/null
+++ b/z2/README.md
@@ -0,0 +1,104 @@
+# Interactive Floor Plan Application with Kubernetes
+
+## Application Description
+
+This application is an interactive floor plan visualization tool that displays sensor data on different floors of a building. Users can navigate between floors using the buttons at the bottom of the page and click on sensors to view their current data.
+
+## Container List
+
+1. **floorplan-webapp**
+ - Web application container based on Python and Flask
+ - Serves the frontend HTML, CSS, and JavaScript files
+ - Provides an API endpoint for sensor data
+
+2. **mysql-db**
+ - MySQL database container
+ - Stores sensor information including position, floor, location, and values
+
+## Kubernetes Objects
+
+1. **Namespace**
+ - `sensor-app`: Isolated environment for all application resources
+
+2. **Deployment**
+ - `floorplan-webapp`: Manages the web application with 2 replicas for high availability
+
+3. **StatefulSet**
+ - `mysql`: Manages the MySQL database instance with persistent storage
+
+4. **Services**
+ - `floorplan-service`: NodePort service exposing the web application on port 30080
+ - `mysql-service`: Headless service for MySQL database access
+
+5. **PersistentVolume**
+ - `mysql-pv`: 1GB volume for MySQL data storage
+
+6. **PersistentVolumeClaim**
+ - `mysql-pvc`: Claims storage from the persistent volume for MySQL
+
+## Network and Storage Configuration
+
+### Networks
+- The application uses Kubernetes' built-in networking
+- Web application connects to MySQL using the `mysql-service` DNS name
+- External access is provided through a NodePort service on port 30080
+
+### Storage
+- The MySQL database uses a persistent volume mounted at `/var/lib/mysql`
+- The persistent volume is backed by a hostPath at `/mnt/data` on the host machine
+
+## Container Configuration
+
+### Web Application Container
+- Based on Python 3.9 slim image
+- Installed packages: Flask, mysql-connector-python
+- Configured to connect to MySQL database using environment variables
+- Exposes port 5000
+
+### MySQL Container
+- Based on MySQL 8.0 image
+- Configured with empty root password for development purposes
+- Initializes with the sensors_db database and sample data
+- Exposes port 3306
+
+## Application Management
+
+### Preparation
+1. Make all scripts executable:
+ ```
+ chmod +x prepare-app.sh start-app.sh stop-app.sh
+ ```
+2. Run the preparation script:
+ ```
+ ./prepare-app.sh
+ ```
+ This will:
+ - Create the required namespace
+ - Prepare the persistent volume directory
+ - Build the Docker images
+
+### Starting the Application
+1. Run the start script:
+ ```
+ ./start-app.sh
+ ```
+ This will:
+ - Apply all Kubernetes configurations
+ - Wait for all components to be ready
+ - Display the URL to access the application
+
+### Stopping the Application
+1. Run the stop script:
+ ```
+ ./stop-app.sh
+ ```
+ This will:
+ - Remove all Kubernetes objects created for the application
+ - The persistent volume data will remain intact
+
+### Viewing the Application
+1. Open your web browser
+2. Navigate to the URL displayed after running the start script (typically http://[NODE-IP]:30080)
+3. You should see the interactive floor plan with sensors
+4. Use the buttons at the bottom to switch between floors
+5. Click on sensors to view their data in the right panel
\ No newline at end of file
diff --git a/z2/app.js b/z2/app.js
new file mode 100644
index 0000000..5d29d5f
--- /dev/null
+++ b/z2/app.js
@@ -0,0 +1,160 @@
+// Global variable to store the current floor
+let currentFloor = 1;
+
+// Fallback data in case API fails
+const fallbackSensors = {
+ 1: [
+ { id: 1, x: 100, y: 100, floor: 1, location: "Room A", value: "22°C" },
+ { id: 2, x: 200, y: 200, floor: 1, location: "Room B", value: "24°C" },
+ ],
+ 2: [
+ { id: 3, x: 150, y: 150, floor: 2, location: "Living Room", value: "23°C" },
+ { id: 4, x: 250, y: 250, floor: 2, location: "Kitchen", value: "21°C" },
+ ],
+ 3: [
+ { id: 5, x: 120, y: 120, floor: 3, location: "Hallway", value: "22°C" },
+ { id: 6, x: 220, y: 220, floor: 3, location: "Bathroom", value: "23°C" },
+ ]
+};
+
+// Function to fetch sensors from the API
+async function fetchSensors() {
+ try {
+ const response = await fetch('/api/sensors');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const data = await response.json();
+ console.log("Fetched sensors:", data);
+
+ if (data.length === 0) {
+ console.warn("No sensors returned from API, using fallback data");
+ return fallbackSensors[currentFloor];
+ }
+
+ return data;
+ } catch (error) {
+ console.error("Could not fetch sensors:", error);
+ console.warn("Using fallback sensor data due to API error");
+ return fallbackSensors[currentFloor];
+ }
+}
+
+// Function to display the map for the corresponding floor
+function showFloor(floor) {
+ console.log(`Showing floor ${floor}`);
+ currentFloor = floor;
+ const map = document.getElementById('map');
+ map.src = `images/floor${floor}.png`;
+
+ // Fetch and display sensors for this floor
+ fetchSensors().then(sensors => {
+ // Filter sensors for the current floor
+ const floorSensors = sensors.filter(sensor => parseInt(sensor.floor) === parseInt(floor));
+ console.log(`Sensors for floor ${floor}:`, floorSensors);
+
+ if (floorSensors.length === 0) {
+ console.warn(`No sensors found for floor ${floor}, adding test sensors`);
+ // Add test sensors if none found
+ addTestSensors();
+ } else {
+ addSensorsToMap(floorSensors);
+ }
+
+ // Clear the sensor data table
+ const table = document.getElementById('sensor-data');
+ table.innerHTML = `
+
+
ID
+
Location
+
Value
+
+ `;
+ });
+}
+
+// Add sensor points to the map
+function addSensorsToMap(floorSensors) {
+ // First clear all previous points
+ const mapContainer = document.getElementById('map-container');
+ const existingPoints = document.querySelectorAll('.sensor');
+ existingPoints.forEach(point => point.remove());
+
+ console.log("Map container:", mapContainer);
+
+ // Check if we have sensors to display
+ if (!floorSensors || floorSensors.length === 0) {
+ console.warn("No sensors found for this floor");
+ return;
+ }
+
+ // Create new points
+ floorSensors.forEach(sensor => {
+ console.log(`Creating sensor point at x:${sensor.x}, y:${sensor.y}`);
+ const sensorPoint = document.createElement('div');
+ sensorPoint.classList.add('sensor');
+ sensorPoint.style.position = 'absolute';
+ sensorPoint.style.left = `${sensor.x}px`;
+ sensorPoint.style.top = `${sensor.y}px`;
+ sensorPoint.style.width = '15px';
+ sensorPoint.style.height = '15px';
+ sensorPoint.style.borderRadius = '50%';
+ sensorPoint.style.backgroundColor = 'red';
+ sensorPoint.style.border = '2px solid black';
+ sensorPoint.style.zIndex = '1000';
+ sensorPoint.setAttribute('data-id', sensor.id);
+ sensorPoint.addEventListener('click', () => showSensorData(sensor));
+
+ mapContainer.appendChild(sensorPoint);
+ });
+}
+
+// Add test sensors if API sensors aren't working
+function addTestSensors() {
+ const testSensors = fallbackSensors[currentFloor];
+ addSensorsToMap(testSensors);
+}
+
+// Display sensor data in the dashboard
+function showSensorData(sensor) {
+ const table = document.getElementById('sensor-data');
+
+ // Clear the table
+ table.innerHTML = `
+
+
ID
+
Location
+
Value
+
+ `;
+
+ // Add a new row with sensor data
+ const row = table.insertRow(1);
+ row.insertCell(0).textContent = sensor.id;
+ row.insertCell(1).textContent = sensor.location;
+ row.insertCell(2).textContent = sensor.value;
+}
+
+// Initialize the app by showing floor 1 when the page loads
+window.onload = function() {
+ console.log("Page loaded, initializing app...");
+ showFloor(1);
+
+ // Add additional test sensor directly to verify positioning
+ setTimeout(() => {
+ const mapContainer = document.getElementById('map-container');
+ const testPoint = document.createElement('div');
+ testPoint.style.position = 'absolute';
+ testPoint.style.left = '300px';
+ testPoint.style.top = '300px';
+ testPoint.style.width = '20px';
+ testPoint.style.height = '20px';
+ testPoint.style.borderRadius = '50%';
+ testPoint.style.backgroundColor = 'blue';
+ testPoint.style.border = '3px solid yellow';
+ testPoint.style.zIndex = '9999';
+
+ mapContainer.appendChild(testPoint);
+ console.log("Test point added to verify positioning");
+ }, 3000);
+};
\ No newline at end of file
diff --git a/z2/app.py b/z2/app.py
new file mode 100644
index 0000000..63e0239
--- /dev/null
+++ b/z2/app.py
@@ -0,0 +1,67 @@
+from flask import Flask, jsonify, send_from_directory
+import mysql.connector
+import os
+import time
+
+app = Flask(__name__, static_folder='.', static_url_path='')
+
+# Database connection with environment variables
+def get_db_connection():
+ # Get environment variables or use defaults
+ host = os.environ.get('MYSQL_HOST', 'mysql-service.sensor-app')
+ user = os.environ.get('MYSQL_USER', 'root')
+ password = os.environ.get('MYSQL_PASSWORD', '')
+ database = os.environ.get('MYSQL_DB', 'sensors_db')
+
+ print(f"Connecting to MySQL: {host}, {user}, {database}")
+
+ # Try to connect with retries for Kubernetes startup sequence
+ max_retries = 50
+ retry_delay = 50
+
+ for attempt in range(max_retries):
+ try:
+ conn = mysql.connector.connect(
+ host=host,
+ user=user,
+ password=password,
+ database=database
+ )
+ print("Database connection successful!")
+ return conn
+ except mysql.connector.Error as err:
+ print(f"Database connection error: {err}")
+ if attempt < max_retries - 1:
+ print(f"Database connection failed, retrying in {retry_delay} seconds...")
+ time.sleep(retry_delay)
+ else:
+ print(f"Failed to connect to database after {max_retries} attempts")
+ raise
+
+@app.route('/api/sensors', methods=['GET'])
+def get_sensors():
+ try:
+ conn = get_db_connection()
+ cursor = conn.cursor(dictionary=True)
+ cursor.execute('SELECT * FROM sensors')
+ sensors = cursor.fetchall()
+ cursor.close()
+ conn.close()
+ print(f"Sensors fetched from database: {sensors}")
+ return jsonify(sensors)
+ except Exception as e:
+ print(f"Error fetching sensors: {e}")
+ # Return empty array instead of error for better frontend handling
+ return jsonify([])
+
+@app.route('/')
+def index():
+ return send_from_directory('.', 'index.html')
+
+# Serve static files
+@app.route('/')
+def static_files(path):
+ return send_from_directory('.', path)
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
\ No newline at end of file
diff --git a/z2/deployment.yaml b/z2/deployment.yaml
new file mode 100644
index 0000000..bc2f582
--- /dev/null
+++ b/z2/deployment.yaml
@@ -0,0 +1,31 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: floorplan-webapp
+ namespace: sensor-app
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: floorplan-webapp
+ template:
+ metadata:
+ labels:
+ app: floorplan-webapp
+ spec:
+ containers:
+ - name: webapp
+ image: floorplan-webapp:latest
+ imagePullPolicy: Never
+ ports:
+ - containerPort: 5000
+ env:
+ - name: MYSQL_HOST
+ value: mysql-service.sensor-app
+ resources:
+ requests:
+ memory: "128Mi"
+ cpu: "100m"
+ limits:
+ memory: "256Mi"
+ cpu: "200m"
\ No newline at end of file
diff --git a/z2/floor1.png b/z2/floor1.png
new file mode 100644
index 0000000..c9299b1
Binary files /dev/null and b/z2/floor1.png differ
diff --git a/z2/floor2.png b/z2/floor2.png
new file mode 100644
index 0000000..e0b32fa
Binary files /dev/null and b/z2/floor2.png differ
diff --git a/z2/floor3.png b/z2/floor3.png
new file mode 100644
index 0000000..be370da
Binary files /dev/null and b/z2/floor3.png differ
diff --git a/z2/index.html b/z2/index.html
new file mode 100644
index 0000000..21b05c9
--- /dev/null
+++ b/z2/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Building Map
+
+
+
+
+
+
Interactive Floor Plan with Sensors
+
+
+
+
+
+
+
+
+
+
Sensor Data
+
+
+
ID
+
Location
+
Value
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh
new file mode 100644
index 0000000..ac77a9a
--- /dev/null
+++ b/z2/prepare-app.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Create namespace
+kubectl create namespace sensor-app
+
+# Create directory for persistent volume if not exists
+sudo mkdir -p /mnt/data
+sudo chmod 777 /mnt/data
+
+# Build Docker images
+echo "Building web application image..."
+docker build -t floorplan-webapp:latest -f Dockerfile .
+
+echo "Building MySQL database image..."
+docker build -t mysql-db:latest -f Dockerfile.mysql .
+
+echo "Application prepared successfully!"
\ No newline at end of file
diff --git a/z2/sensors_db.sql b/z2/sensors_db.sql
new file mode 100644
index 0000000..d44221a
Binary files /dev/null and b/z2/sensors_db.sql differ
diff --git a/z2/service.yaml b/z2/service.yaml
new file mode 100644
index 0000000..cc38513
--- /dev/null
+++ b/z2/service.yaml
@@ -0,0 +1,26 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: floorplan-service
+ namespace: sensor-app
+spec:
+ type: NodePort
+ ports:
+ - port: 80
+ targetPort: 5000
+ nodePort: 30080
+ selector:
+ app: floorplan-webapp
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: mysql-service
+ namespace: sensor-app
+spec:
+ ports:
+ - port: 3306
+ targetPort: 3306
+ selector:
+ app: mysql
+ clusterIP: None # Headless service for StatefulSet
\ No newline at end of file
diff --git a/z2/start-app.sh b/z2/start-app.sh
new file mode 100644
index 0000000..5b413d3
--- /dev/null
+++ b/z2/start-app.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Apply Kubernetes configurations
+echo "Creating Kubernetes resources..."
+
+# Create namespace if it doesn't exist
+kubectl get namespace sensor-app || kubectl create namespace sensor-app
+
+# Apply configurations
+echo "Applying PersistentVolume, PersistentVolumeClaim, and StatefulSet..."
+kubectl apply -f statefulset.yaml
+
+echo "Waiting for MySQL StatefulSet to be ready..."
+kubectl wait --for=condition=ready pod -l app=mysql --timeout=120s -n sensor-app
+
+echo "Applying Deployment..."
+kubectl apply -f deployment.yaml
+
+echo "Applying Service..."
+kubectl apply -f service.yaml
+
+echo "Waiting for application to be ready..."
+kubectl wait --for=condition=ready pod -l app=floorplan-webapp --timeout=60s -n sensor-app
+
+LOCAL_PORT=8080
+echo ""
+echo "Application started successfully!"
+echo "Setting up port forwarding from localhost:$LOCAL_PORT to the service..."
+echo "You can access the application at: http://localhost:$LOCAL_PORT"
+echo ""
+echo "Press Ctrl+C to stop port forwarding when done"
+
+# Start port forwarding
+kubectl port-forward -n sensor-app service/floorplan-service $LOCAL_PORT:80
\ No newline at end of file
diff --git a/z2/statefulset.yaml b/z2/statefulset.yaml
new file mode 100644
index 0000000..5413344
--- /dev/null
+++ b/z2/statefulset.yaml
@@ -0,0 +1,66 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: mysql-pv
+ namespace: sensor-app
+ labels:
+ type: local
+spec:
+ storageClassName: manual
+ capacity:
+ storage: 1Gi
+ accessModes:
+ - ReadWriteOnce
+ hostPath:
+ path: "/mnt/data"
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: mysql-pvc
+ namespace: sensor-app
+spec:
+ storageClassName: manual
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Gi
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: mysql
+ namespace: sensor-app
+spec:
+ serviceName: mysql-service
+ replicas: 1
+ selector:
+ matchLabels:
+ app: mysql
+ template:
+ metadata:
+ labels:
+ app: mysql
+ spec:
+ containers:
+ - name: mysql
+ image: mysql-db:latest
+ imagePullPolicy: Never
+ ports:
+ - containerPort: 3306
+ name: mysql
+ env:
+ - name: MYSQL_ALLOW_EMPTY_PASSWORD
+ value: "true"
+ volumeMounts:
+ - name: mysql-persistent-storage
+ mountPath: /var/lib/mysql
+ resources:
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+ volumes:
+ - name: mysql-persistent-storage
+ persistentVolumeClaim:
+ claimName: mysql-pvc
\ No newline at end of file
diff --git a/z2/stop-app.sh b/z2/stop-app.sh
new file mode 100644
index 0000000..6d449c9
--- /dev/null
+++ b/z2/stop-app.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+echo "Stopping and removing Kubernetes resources..."
+
+# Delete all resources in the namespace
+kubectl delete -f service.yaml
+kubectl delete -f deployment.yaml
+kubectl delete -f statefulset.yaml
+
+echo "Application stopped successfully!"
\ No newline at end of file
diff --git a/z2/styles.css b/z2/styles.css
new file mode 100644
index 0000000..fc69123
--- /dev/null
+++ b/z2/styles.css
@@ -0,0 +1,96 @@
+/* General Styles */
+body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ background-color: #f4f4f4;
+}
+
+header {
+ background-color: #333;
+ color: white;
+ text-align: center;
+ padding: 20px;
+}
+
+.main-content {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ margin: 20px;
+}
+
+#map-container {
+ position: relative;
+ width: 70%;
+ height: 500px;
+ border: 1px solid #ddd;
+ overflow: hidden;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ cursor: pointer;
+}
+
+/* Sensor point styling */
+.sensor {
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+ background-color: red;
+ border: 2px solid black;
+ cursor: pointer;
+ z-index: 100;
+ transition: transform 0.2s;
+}
+
+.sensor:hover {
+ transform: scale(1.5);
+}
+
+#dashboard {
+ width: 25%;
+ padding: 20px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ margin-left: 20px;
+}
+
+#sensor-data {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+#sensor-data th, #sensor-data td {
+ padding: 8px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+}
+
+/* Button Styles */
+.buttons-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+button {
+ padding: 10px 20px;
+ margin: 5px;
+ cursor: pointer;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ transition: background-color 0.3s;
+}
+
+button:hover {
+ background-color: #45a049;
+}
\ No newline at end of file