Skip to content
Snippets Groups Projects
Commit 539eb15a authored by Nikos Filinis's avatar Nikos Filinis
Browse files

Merge branch 'backend-refactor' into 'main'

Backend refactor

See merge request !4
parents 8e4c2339 8880da02
No related branches found
No related tags found
1 merge request!4Backend refactor
Pipeline #59939 passed with stage
in 3 minutes and 14 seconds
Showing
with 625 additions and 695 deletions
......@@ -146,3 +146,5 @@ poetry.toml
# LSP config files
pyrightconfig.json
dashboard.db
......@@ -35,12 +35,84 @@ make push
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
```
3. Install the helm chart:
3. Create a Persistent Volume and a Persistent Volume Claim by running `kubectl apply -f pv.yaml` where `pv.yaml` contains a PersistentVolume like:
```yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: postgres-pv
namespace: nephele-platform
labels:
type: local
app: postgres
spec:
storageClassName: "standard"
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: nephele-platform
labels:
app: postgres
spec:
storageClassName: "standard"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
```
4. Create three secrets with the backend, Postgres and Keycloak credentials. Run `kubectl apply -f secrets.yaml` where `secrets.yaml` is a file:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-credentials
namespace: nephele-platform
type: Opaque
stringData:
POSTGRES_USER: XXXXXXXXX
POSTGRES_PASSWORD: XXXXXXXXX
POSTGRES_DB: XXXXXXXXX
---
apiVersion: v1
kind: Secret
metadata:
name: keycloak-secrets
namespace: nephele-platform
type: Opaque
stringData:
KEYCLOAK_ADMIN: XXXXXXXXX
KEYCLOAK_ADMIN_PASSWORD: XXXXXXXXX
---
apiVersion: v1
kind: Secret
metadata:
name: nephele-backend
namespace: nephele-platform
type: Opaque
stringData:
FLASK_USERNAME: XXXXXXXXX
FLASK_PASSWORD: XXXXXXXXX
CLIENT_ID: XXXXXXXXX
CLIENT_SECRET: XXXXXXXXX
```
5. Install the helm chart:
```bash
helmchart$ helm install platform . --namespace nephele-platform --create-namespace
```
4. Upload the Keycloak realm json file.
6. Upload the Keycloak realm json file.
## Backend
### Run docker compose that runs keycloak, postgresql, flask_backend, volume and network
......
"""Main Flask app entrypoint."""
import os
import json
from flasgger import Swagger
from flask import Flask
from flask_cors import CORS
from config import configs
from errors import errors, error_handlers
from models import db
from blueprints.artifact import artifact
from blueprints.cluster import cluster
from blueprints.dashboard import dashboard
from flasgger import Swagger
from flask_cors import CORS, cross_origin
from blueprints.device import device
from blueprints.hdag import hdag
from blueprints.user import user
from models.utils import add_initial_data
env = os.environ.get('FLASK_ENV', 'production')
......@@ -19,6 +26,7 @@ def create_app(app_name='dashboard'):
app = Flask(app_name, static_url_path='', static_folder='nephele-front/dist/spa/')
swagger = Swagger(app)
# TODO: do we need CORS if hosted on the same domain?
cors = CORS(app)
app.config.from_object(configs[env])
......@@ -27,23 +35,18 @@ def create_app(app_name='dashboard'):
# Load other configurations as needed
app.config.from_object(configs[env])
app.register_blueprint(artifact)
app.register_blueprint(cluster)
app.register_blueprint(dashboard)
app.register_blueprint(device)
app.register_blueprint(hdag)
app.register_blueprint(user)
app.register_error_handler(
errors.SubprocessError,
error_handlers.handle_subprocess_error
)
app.register_error_handler(
errors.YamlReadError,
error_handlers.handle_yaml_read_error
)
db.init_app(app)
with app.app_context():
db.drop_all() # Delete previously created tables
db.drop_all()
db.create_all()
# Add initial data to the database
from models import add_initial_data
add_initial_data()
return app
......@@ -51,7 +54,4 @@ def create_app(app_name='dashboard'):
if __name__ == '__main__':
app = create_app()
# app.run(ssl_context=("../certbot/conf/live/nephele.netmode.ece.ntua.gr/cert.pem", "../certbot/conf/live/nephele.netmode.ece.ntua.gr/privkey.pem"))
# app.run(ssl_context=("/app/certbot/conf/live/nephele.netmode.ece.ntua.gr/cert.pem", "/app/certbot/conf/live/nephele.netmode.ece.ntua.gr/privkey.pem"))
# app.run(ssl_context=("/app/certbot/certs/fullchain.pem", "/app/certbot/certs/privkey.pem"))
app.run()
\ No newline at end of file
app.run()
import base64
import requests
from flasgger import swag_from
from flask import Blueprint, request, jsonify, current_app
from utils.keycloak_client import keycloak_client
artifact = Blueprint('artifact', __name__)
@artifact.route('/fetch_artifacts', methods=['POST'])
@swag_from('swagger_docs/fetch_artifacts.yml')
def fetch_artifacts():
hdar_base_url = current_app.config["HDAR_BASE_URL"]
url = f'{hdar_base_url}/catalogue'
data = request.json
if not data:
return jsonify({'error': 'No JSON data provided'}), 400
# Extract the artifact type and project name from the JSON data
# artiface type can be one of the following [VO,CVO,HDAG,APP]
artifact_type = data.get('artifact_type')
project_name = data.get('project_name')
# Construct parameters based on the provided artifact type
params = {'artifactType': artifact_type}
headers = {'accept': 'application/json'}
# Perform the initial request to the first endpoint
response = requests.get(url, params=params, headers=headers)
if response.status_code != 202:
return jsonify({'error': f"Failed to fetch data from {url}"}), 500
# Filter the responses based on the project_name
filtered_responses = [r for r in response.json() if project_name in r]
# Extract artifact IDs from the filtered responses
artifact_ids = [r.split('/')[-1] for r in filtered_responses]
# Fetch information for each artifact ID
artifact_info = {}
for artifact_id in artifact_ids:
artifact_url = f'{hdar_base_url}/catalogue/{artifact_id}/config'
params = {'project': {project_name}}
headers = {'accept': 'application/json'}
artifact_response = requests.get(artifact_url, params=params, headers=headers)
if artifact_response.status_code == 200:
artifact_info[artifact_id] = artifact_response.json()
print ("id", artifact_id)
else:
artifact_info[artifact_id] = {'error': f"Failed to fetch data from {artifact_url}"}
return jsonify(artifact_info)
@artifact.route('/fetch_artifacts_information', methods=['POST'])
@swag_from('swagger_docs/fetch_artifacts_information.yml')
def fetch_artifacts_information():
hdar_base_url = current_app.config["HDAR_BASE_URL"]
data = request.json
artifact_id = data.get('artifact_id')
project_name = data.get('project_name')
# Hit the endpoint with the provided artifact ID and project name
url = f'{hdar_base_url}/catalogue/{artifact_id}/content'
params = {'project': project_name}
headers = {'accept': 'application/json'}
response = requests.get(url, params=params, headers=headers)
if response.status_code == 200:
decoded_content = []
for artifact in response.json():
content_bytes = base64.b64decode(artifact['content'])
decoded_content.append({'fileName': artifact['fileName'], 'content': content_bytes.decode('utf-8')})
return jsonify(decoded_content)
else:
return jsonify({'error': f"Failed to fetch data from {url}"}), response.status_code
from flasgger import swag_from
from flask import Blueprint, jsonify
from models import db, NepheleCluster
from utils.utils import login_required
cluster = Blueprint('cluster', __name__)
@cluster.route('/clusters', methods=['GET'])
@swag_from('swagger_docs/clusters.yml')
@login_required
def get_clusters():
clusters = db.session.query(NepheleCluster).all()
cluster_list = [cluster.to_dict() for cluster in clusters]
return jsonify({'clusters': cluster_list}), 200
This diff is collapsed.
from flasgger import swag_from
from flask import Blueprint, jsonify
from models import db, RegisteredIoTDevice
from utils.utils import login_required
device = Blueprint('device', __name__)
@device.route('/devices', methods=['GET'])
@swag_from('swagger_docs/devices.yml')
@login_required
def get_devices():
devices = db.session.query(RegisteredIoTDevice).all()
device_list = [device.to_dict() for device in devices]
return jsonify({'devices': device_list}), 200
\ No newline at end of file
import requests
from flasgger import swag_from
from flask import Blueprint, request, jsonify, current_app
hdag = Blueprint('hdag', __name__)
@hdag.route('/publish_hdag', methods=['POST'])
@swag_from('swagger_docs/publish_hdag.yml')
def publish_hdag():
hdar_base_url = current_app.config["HDAR_BASE_URL"]
smo_base_url = current_app.config["SMO_BASE_URL"]
data = request.json
project_name = data.get('project_name')
if not project_name:
return jsonify({'error': 'Project name is missing in the request parameters'}), 400
descriptor_json = data.get('hdag_descriptor') # Assuming the request body contains JSON data
url = f'{hdar_base_url}/hdag?project={project_name}'
headers = {'accept': 'text/plain', 'Content-Type': 'application/json'}
# response = requests.post(url, json=descriptor_json, headers=headers)
if True: #response.status_code == 200:
url = f'{smo_base_url}/graph/project/{project_name}'
headers = {'Content-Type': 'application/json'}
response = requests.post(url, headers=headers, json=descriptor_json)
return response.text #TODO propably trigger SMO! Send also the project in which is the HDAG
else:
return f"Error: {response.status_code} - {response.text}", response.status_code
#TODO Test and finalize it is not working
@hdag.route('/update_hdag', methods=['POST'])
# @swag_from('swagger_docs/update_hdag.yml')
def update_hdag():
hdar_base_url = current_app.config["HDAR_BASE_URL"]
data = request.json
project_name = data.get('project_name')
if not project_name:
return jsonify({'error': 'Project name is missing in the request parameters'}), 400
descriptor_json = data.get('hdag_descriptor') # Assuming the request body contains JSON data
url = f'{hdar_base_url}/hdag?project={project_name}'
headers = {'accept': 'text/plain', 'Content-Type': 'application/json'}
response = requests.post(url, json=descriptor_json, headers=headers)
print (response.status_code)
if response.status_code == 200:
return response.text #TODO propably trigger SMO! Send also the project in which is the HDAG
else:
return f"Error: {response.status_code} - {response.text}", response.status_code
@hdag.route('/app_graphs', methods=['POST'])
@swag_from('swagger_docs/app_graphs.yml')
def app_graphs():
smo_base_url = current_app.config["SMO_BASE_URL"]
data = request.json
project_name = data.get('project_name')
url = f'{smo_base_url}/graph/project/{project_name}'
response = requests.get(url)
data = response.json()
return jsonify(data)
@hdag.route('/stop_graph', methods=['POST'])
def stop_graph():
smo_base_url = current_app.config["SMO_BASE_URL"]
data = request.json
graph_name = data.get("graph_name")
print (graph_name)
url = f'{smo_base_url}/graph/{graph_name}/stop'
response = requests.get(url)
print ("stop")
return jsonify({'Graph': "is Stopping"}), 200
@hdag.route('/start_graph', methods=['POST'])
def start_graph():
smo_base_url = current_app.config["SMO_BASE_URL"]
data = request.json
graph_name = data.get("graph_name")
print (graph_name)
url = f'{smo_base_url}/graph/{graph_name}/start'
response = requests.get(url)
print ("start")
return jsonify({'Graph': "is Starting"}), 200
from flasgger import swag_from
from flask import Blueprint, request, jsonify
from keycloak import KeycloakPostError, KeycloakPutError, KeycloakAuthenticationError
from utils.utils import login_required
from utils.keycloak_client import keycloak_client
user = Blueprint('user', __name__)
@user.route('/user_groups', methods=['POST'])
@login_required
@swag_from('swagger_docs/user_groups.yml')
def get_user_groups():
# Extract access token from request headers
access_token_header = request.headers.get('Authorization')
if access_token_header and access_token_header.startswith('Bearer '):
access_token = access_token_header.split(' ')[1] # Extract the token part after 'Bearer '
if not access_token:
return jsonify({'error': 'Access token missing'}), 401
# Get userinfo from Keycloak using the access token
print ("access token", access_token)
try:
userinfo = keycloak_client.keycloak_openid.userinfo(access_token)
except KeycloakAuthenticationError:
return jsonify({'error': 'Access token invalid'}), 401
if not userinfo:
return jsonify({'error': 'Failed to get userinfo from Keycloak'}), 401
# Extract username from userinfo
username = userinfo.get('preferred_username')
if not username:
return jsonify({'error': 'Username not found in userinfo'}), 401
user_id = keycloak_client.keycloak_admin.get_user_id(username=username)
# Get groups that the user belongs to using KeycloakAdmin
user_groups = keycloak_client.keycloak_admin.get_user_groups(user_id=user_id)
if not user_groups:
return jsonify({'error': 'Failed to get user groups from Keycloak'}), 404
# Everything went ok return groups for specific username
return jsonify({'username': username, 'groups': user_groups})
else:
return jsonify({'error': 'Access denied'}), 401
# Route for creating a new project
@user.route('/create_project', methods=['POST'])
@login_required
@swag_from('swagger_docs/create_project.yml')
def create_project():
# Ensure backend has a valid access token
if not keycloak_client.backend_access_token:
return jsonify({'error': 'Failed to obtain backend access token'}), 500
# Extract project data from the request
project_name = request.form['project']
# Create group
try:
group_id = keycloak_client.keycloak_admin.create_group(payload={"name": project_name})
except KeycloakPostError:
group_id = keycloak_client.keycloak_admin.get_groups(query={"name": project_name})
print (jsonify({'error': 'Failed to create project group. Group already exists'}))
# Add the current user to the newly created group
# Intercept requests and check group access
access_token_header = request.headers.get('Authorization')
if access_token_header and access_token_header.startswith('Bearer '):
access_token = access_token_header.split(' ')[1] # Extract the token part after 'Bearer '
if not access_token:
return jsonify({'error': 'Access token missing'}), 401
# Fetch userinfo using access token
try:
userinfo = keycloak_client.keycloak_openid.userinfo(access_token)
except KeycloakAuthenticationError:
return jsonify({'error': 'Access denied'}), 401
try:
userinfo = keycloak_client.keycloak_openid.userinfo(access_token)
except KeycloakAuthenticationError:
return jsonify({'error': 'Access denied'}), 401
username = userinfo.get('preferred_username')
if not username:
return jsonify({'error': 'Username not found in userinfo'}), 401
user_id = keycloak_client.keycloak_admin.get_user_id(username=username)
if user_id:
try:
keycloak_client.keycloak_admin.group_user_add(user_id, group_id)
except KeycloakPutError:
return jsonify({'message': 'User already belong to group'})
else:
return jsonify({'message': 'User does not exist'})
return jsonify({'message': 'Project created successfully and User is Assigned to Group'})
@user.route('/refresh_token', methods=['POST'])
@swag_from('swagger_docs/refresh_token.yml')
def refresh_token():
refresh_token = request.form['refresh_token']
try:
tokens = keycloak_client.keycloak_openid.refresh_token(refresh_token)
except KeycloakPostError:
return jsonify({'error': 'You have to login again'}), 403
access_token = tokens['access_token']
if access_token:
return jsonify({'access_token': access_token})
else:
return jsonify({'error': 'Token refresh failed'}), 401
# Registration route
@user.route('/register', methods=['POST'])
@swag_from('swagger_docs/register.yml')
def register():
# Get registration data from the request
data = request.json
# Extract username, password, and email from the JSON data
username = data.get('username')
password = data.get('password')
email = data.get('email')
payload = {
'username': username,
'email': email,
'enabled': True, # Enable the user automatically
'credentials': [{
'type': 'password',
'value': password,
'temporary': False
}]
}
#TODO be sure that the admin has refreshed token!!
# Register user
# Add any additional fields required by Keycloak for user registration
try:
user_id = keycloak_client.keycloak_admin.create_user(payload=payload)
except KeycloakPostError:
return jsonify({'error': 'User exists'}), 409
# Assign role to user
role_name = 'developer'
role_id = keycloak_client.keycloak_admin.get_realm_role(role_name)
if role_id:
keycloak_client.keycloak_admin.assign_realm_roles(user_id=user_id, roles= role_id)
print(f"Role '{role_name}' assigned to user '{username}' successfully.")
else:
print(f"Role '{role_name}' not found.")
if user_id:
return jsonify({'message': 'User registered successfully'}), 200
else:
return jsonify({'error': 'Failed to register user'}), 500
# Login route
@user.route('/login', methods=['POST'])
@swag_from('swagger_docs/login.yml')
def login():
data = request.json
username = data.get('username')
password = data.get('password')
access_token, refresh_token = keycloak_client.obtain_user_tokens(username, password)
if access_token and refresh_token:
access_token, refresh_token = keycloak_client.obtain_user_tokens(username, password)
# Authentication successful, return tokens as JSON response
return jsonify({'access_token': access_token, 'refresh_token': refresh_token})
else:
return jsonify({'error': 'Login failed'}), 401
import os
import json
from dotenv import load_dotenv
# Get the path of the directory where the Flask app is located
app_dir = os.path.abspath(os.path.dirname(__file__))
# Set the SQLite database path to the Flask app directory
SQLITE_DATABASE_PATH = os.path.join(app_dir, 'dashboard.db')
load_dotenv()
......@@ -22,6 +19,9 @@ class Config:
SECRET_KEY = os.getenv('SECRET_KEY', 'your_secret_key')
FORCE_HTTPS = False
HDAR_BASE_URL = os.getenv('HDAR_BASE_URL')
SMO_BASE_URL = os.getenv('SMO_BASE_URL')
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(SQLITE_DATABASE_PATH)
......@@ -39,4 +39,4 @@ configs = {
'development': DevConfig,
}
dashboard_config = Config()
\ No newline at end of file
dashboard_config = Config()
File deleted
"""Flask error handlers."""
from flask import jsonify
def handle_subprocess_error(e):
response = {
'error': e.description,
'message': e.message
}
return jsonify(response), e.code
def handle_yaml_read_error(e):
response = {
'error': e.description,
'message': e.message
}
return jsonify(response), e.code
"""Flask error classes."""
import subprocess
import yaml
class SubprocessError(subprocess.CalledProcessError):
code = 500
description = 'Subprocess error'
class YamlReadError(yaml.YAMLError):
code = 500
description = 'Yaml read error'
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class NepheleCluster(db.Model):
id = db.Column(db.Integer, primary_key=True)
cluster_name = db.Column(db.String(100), nullable=False)
location = db.Column(db.String(100), nullable=False)
available_cpu = db.Column(db.String, nullable=False)
available_ram = db.Column(db.String, nullable=False)
availability = db.Column(db.Boolean, nullable=False)
grafana = db.Column(db.String, nullable=False)
class RegisteredIoTDevice(db.Model):
id = db.Column(db.Integer, primary_key=True)
device_type = db.Column(db.String(100), nullable=False)
ip = db.Column(db.String(100), nullable=False)
thing_descriptor = db.Column(db.JSON, nullable=False)
location = db.Column(db.String(100), nullable=False)
def update_cluster(cluster_id, data):
cluster = NepheleCluster.query.get(cluster_id)
if not cluster:
return False
cluster.cluster_name = data.get('cluster_name', cluster.cluster_name)
cluster.location = data.get('location', cluster.location)
cluster.available_ram = data.get('available_ram', cluster.available_ram)
cluster.available_cpu = data.get('available_cpu', cluster.available_cpu)
cluster.availability = data.get('availability', cluster.availability)
db.session.commit()
return True
def update_device(device_id, data):
device = RegisteredIoTDevice.query.get(device_id)
if not device:
return False
device.device_type = data.get('device_type', device.device_type)
device.ip = data.get('ip', device.ip)
device.thing_descriptor = data.get('thing_descriptor', device.thing_descriptor)
device.location = data.get('location', device.location)
db.session.commit()
return True
# Function to add initial data to tables
def add_initial_data():
# Add initial clusters
cluster1 = NepheleCluster(cluster_name='Netmode', location='NTUA', available_cpu="72 vCPUs", available_ram="234 GiB", availability=True, grafana="http://10.0.2.114:30150/d/dUMN5x0mk/first-cluster?orgId=1")
cluster2 = NepheleCluster(cluster_name='CNIT', location='Italy', available_cpu="50 vCPUs", available_ram="150 GiB", availability=True, grafana="http://10.0.2.114:30150/d/dUMN5x0mk1/second-cluster?orgId=1")
db.session.add_all([cluster1, cluster2])
# Add initial devices
device1 = RegisteredIoTDevice(device_type='Raspberry Pi', ip='147.102.13.100', thing_descriptor={
"title": "vo",
"id": "urn:dev:wot:plenary:vo",
"description": "Image detection VO.",
"securityDefinitions": {
"no_sc": {
"scheme": "nosec"
}
},
"security": "no_sc",
"@context": [
"https://www.w3.org/2022/wot/td/v1.1"
],
"actions": {
"detectImage": {
"type": "boolean"
}
}
# "title": "device1",
# "id": "urn:dev:wot:plenary:device1",
# "description": "'Lille Plenary Meeting Descriptor for Device 1 (with computing capabilities).'",
# "securityDefinitions": {
# "basic_sec": {
# "scheme": "basic"
# }
# },
# "security": "basic_sec",
# "@context": [
# "https://www.w3.org/2022/wot/td/v1.1"
# ],
# "properties": {
# "temperature": {
# "type": "integer"
# },
# "humidity": {
# "type": "integer"
# }
# },
# "actions": {
# "currentValues": {
# "description": "Returns the current values"
# }
# }
}, location='Netmode')
db.session.add_all([device1])
db.session.commit()
\ No newline at end of file
"""DB models declaration."""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from models.nephele_cluster import NepheleCluster
from models.registered_iot_device import RegisteredIoTDevice
"""Cluster table."""
from models import db
class NepheleCluster(db.Model):
id = db.Column(db.Integer, primary_key=True)
cluster_name = db.Column(db.String(100), nullable=False)
location = db.Column(db.String(100), nullable=False)
available_cpu = db.Column(db.String, nullable=False)
available_ram = db.Column(db.String, nullable=False)
availability = db.Column(db.Boolean, nullable=False)
grafana = db.Column(db.String, nullable=False)
def to_dict(self):
"""Returns a dictionary representation of the class."""
return {
'id': self.id,
'cluster_name': self.cluster_name,
'location': self.location,
'available_cpu': self.available_cpu,
'available_ram': self.available_ram,
'availability': self.availability,
'grafana': self.grafana
}
\ No newline at end of file
"""IoT device table."""
from models import db
class RegisteredIoTDevice(db.Model):
id = db.Column(db.Integer, primary_key=True)
device_type = db.Column(db.String(100), nullable=False)
ip = db.Column(db.String(100), nullable=False)
thing_descriptor = db.Column(db.JSON, nullable=False)
location = db.Column(db.String(100), nullable=False)
def to_dict(self):
"""Returns a dictionary representation of the class."""
return {
'id': self.id,
'device_type': self.device_type,
'ip': self.ip,
'thing_descriptor': self.thing_descriptor,
'location': self.location
}
\ No newline at end of file
from models import db, NepheleCluster, RegisteredIoTDevice
def update_cluster(cluster_id, data):
cluster = db.session.query(NepheleCluster).get(cluster_id)
if not cluster:
return False
cluster.cluster_name = data.get('cluster_name', cluster.cluster_name)
cluster.location = data.get('location', cluster.location)
cluster.available_ram = data.get('available_ram', cluster.available_ram)
cluster.available_cpu = data.get('available_cpu', cluster.available_cpu)
cluster.availability = data.get('availability', cluster.availability)
db.session.commit()
return True
def update_device(device_id, data):
device = db.session.query(RegisteredIoTDevice).get(device_id)
if not device:
return False
device.device_type = data.get('device_type', device.device_type)
device.ip = data.get('ip', device.ip)
device.thing_descriptor = data.get('thing_descriptor', device.thing_descriptor)
device.location = data.get('location', device.location)
db.session.commit()
return True
# Function to add initial data to tables
def add_initial_data():
# Add initial clusters
cluster1 = NepheleCluster(
cluster_name='Netmode',
location='NTUA',
available_cpu="72 vCPUs",
available_ram="234 GiB",
availability=True,
grafana="http://10.0.2.114:30150/d/dUMN5x0mk/first-cluster?orgId=1"
)
cluster2 = NepheleCluster(
cluster_name='CNIT',
location='Italy',
available_cpu="50 vCPUs",
available_ram="150 GiB",
availability=True,
grafana="http://10.0.2.114:30150/d/dUMN5x0mk1/second-cluster?orgId=1"
)
db.session.add_all([cluster1, cluster2])
# Add initial devices
TD = {
"title": "vo",
"id": "urn:dev:wot:plenary:vo",
"description": "Image detection VO.",
"securityDefinitions": {
"no_sc": {
"scheme": "nosec"
}
},
"security": "no_sc",
"@context": [
"https://www.w3.org/2022/wot/td/v1.1"
],
"actions": {
"detectImage": {
"type": "boolean"
}
}
# "title": "device1",
# "id": "urn:dev:wot:plenary:device1",
# "description": "'Lille Plenary Meeting Descriptor for Device 1 (with computing capabilities).'",
# "securityDefinitions": {
# "basic_sec": {
# "scheme": "basic"
# }
# },
# "security": "basic_sec",
# "@context": [
# "https://www.w3.org/2022/wot/td/v1.1"
# ],
# "properties": {
# "temperature": {
# "type": "integer"
# },
# "humidity": {
# "type": "integer"
# }
# },
# "actions": {
# "currentValues": {
# "description": "Returns the current values"
# }
# }
}
device1 = RegisteredIoTDevice(
device_type='Raspberry Pi',
ip='147.102.13.100',
thing_descriptor=TD,
location='Netmode'
)
db.session.add_all([device1])
db.session.commit()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Project</title>
</head>
<body>
<h1>Create a New Project</h1>
<form action="/create_project" method="post">
<label for="project">Project Name:</label><br>
<input type="text" id="project" name="project"><br><br>
<input type="submit" value="Create Project">
</form>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Homepage</title>
</head>
<body>
<h1>Welcome to the Homepage</h1>
<p>This is the homepage content.</p>
<a href="/create_project">Create Project</a><br>
<a href="/logout">Logout</a>
</body>
</html>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment