Upload files to "z2"

This commit is contained in:
Danylo Kunak 2025-04-16 22:28:25 +00:00
parent e7da16d9c6
commit 997111a7ba
17 changed files with 671 additions and 0 deletions

11
z2/Dockerfile Normal file
View File

@ -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"]

7
z2/Dockerfile.mysql Normal file
View File

@ -0,0 +1,7 @@
FROM mysql:8.0
ENV MYSQL_ALLOW_EMPTY_PASSWORD=true
COPY sensors_db.sql /docker-entrypoint-initdb.d/
EXPOSE 3306

104
z2/README.md Normal file
View File

@ -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

160
z2/app.js Normal file
View File

@ -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 = `
<tr>
<th>ID</th>
<th>Location</th>
<th>Value</th>
</tr>
`;
});
}
// 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 = `
<tr>
<th>ID</th>
<th>Location</th>
<th>Value</th>
</tr>
`;
// 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);
};

67
z2/app.py Normal file
View File

@ -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('/<path:path>')
def static_files(path):
return send_from_directory('.', path)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')

31
z2/deployment.yaml Normal file
View File

@ -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"

BIN
z2/floor1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
z2/floor2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
z2/floor3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

42
z2/index.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Building Map</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="header-content">
<h1>Interactive Floor Plan with Sensors</h1>
</div>
</header>
<div class="main-content">
<div id="map-container">
<img id="map" src="images/floor1.png" alt="Floor 1">
<!-- Sensor points will be added here dynamically -->
</div>
<div id="dashboard">
<h2>Sensor Data</h2>
<table id="sensor-data">
<tr>
<th>ID</th>
<th>Location</th>
<th>Value</th>
</tr>
<!-- Sensor data will be inserted here -->
</table>
</div>
</div>
<div class="buttons-container">
<button onclick="showFloor(1)">Floor 1</button>
<button onclick="showFloor(2)">Floor 2</button>
<button onclick="showFloor(3)">Floor 3</button>
</div>
<script src="app.js"></script>
</body>
</html>

17
z2/prepare-app.sh Normal file
View File

@ -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!"

BIN
z2/sensors_db.sql Normal file

Binary file not shown.

26
z2/service.yaml Normal file
View File

@ -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

34
z2/start-app.sh Normal file
View File

@ -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

66
z2/statefulset.yaml Normal file
View File

@ -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

10
z2/stop-app.sh Normal file
View File

@ -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!"

96
z2/styles.css Normal file
View File

@ -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;
}