Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6580845
feat: Add ContainerLabs menu, Running/Create CLabs submenus, and file…
DJeanS03 Sep 19, 2025
98b763f
fix: improve YAML validation and handling of submit button state
DJeanS03 Sep 19, 2025
411bb09
feat(clabs): consolidar Create CLab — validação YAML, DnD de pastas e…
DJeanS03 Sep 21, 2025
b0e7085
test(clabs): add valid and invalid ContainerLab YAML examples for Cre…
DJeanS03 Sep 21, 2025
43b6ba3
feat(clabs): unify persisted state path and ensure JSON save/load acr…
DJeanS03 Sep 21, 2025
f7664c5
feat(clabs): persist mock state to JSON + reload on start + TTL prune
DJeanS03 Sep 21, 2025
3ceefc8
refactor(app): load optional blueprints via OPTIONAL_MODULES; always …
DJeanS03 Oct 3, 2025
3355839
fix(config): load OPTIONAL_MODULES/ENABLE_CLABS by defining inside Co…
DJeanS03 Oct 3, 2025
8e665ca
feat(clabs): add DELETE endpoint and wire running.html to remove CLabs
DJeanS03 Oct 3, 2025
ff00918
refactor(clabs): wire routes to C9sController and drop mock/store usage
DJeanS03 Oct 3, 2025
c5ce828
clabs(routes): thin controllers + authZ por dono/role; validação YAML…
DJeanS03 Oct 4, 2025
8c81eab
config/clabs: controller owns state path default; remove config key
DJeanS03 Oct 4, 2025
a2e6cf2
clabs(cleanup): remove PoC mocks/store files
DJeanS03 Oct 5, 2025
b177369
:wrench: chore: atualizar .gitignore
DJeanS03 Oct 12, 2025
df65d39
:sparkles: feat: models Clab/ClabInstance
DJeanS03 Oct 12, 2025
09c68df
feat(clabs): add ORM relations & unique (namespace_default,title) ind…
DJeanS03 Oct 21, 2025
c5d5f7b
feat(clabs): migrate containerlabs to DB with serialization helpers
DJeanS03 Oct 21, 2025
d6031af
feat(clabs): add status/extend APIs with RBAC & caps; expose expires_…
DJeanS03 Oct 21, 2025
e6f4594
feat(users): persist DataTables search state and disable browser auto…
DJeanS03 Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ __pycache__/

# venv
venv
*.venv

# .env variables file
.env
Expand All @@ -35,3 +36,5 @@ apps/static/assets/.temp

*.pem
kube-config.yaml

*.sh text eol=lf
23 changes: 19 additions & 4 deletions apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,26 @@ def register_extensions(app):


def register_blueprints(app):
# Core blueprints (always loaded)
for module_name in ('authentication', 'home', 'api', 'k8s', 'cli'):
module = import_module('apps.{}.routes'.format(module_name))
module = import_module(f'apps.{module_name}.routes')
app.register_blueprint(module.blueprint)
# Load socketio endpoints
import_module("apps.events")

# Optional blueprints (controlled by config.OPTIONAL_MODULES)
optional_modules = app.config.get("OPTIONAL_MODULES", []) or []
for opt in optional_modules:
try:
mod = import_module(f"apps.{opt}.routes")
app.register_blueprint(mod.blueprint)
app.logger.info(f"Optional module loaded: {opt}")
except Exception as e:
app.logger.error(f"Failed to load optional module '{opt}': {e}")

# Socket.IO endpoints (xterm) - always load; Windows must run via Docker (see docs)
try:
import_module("apps.events")
except Exception as e:
app.logger.error(f"Failed to load apps.events: {e}")

def configure_database(app):
with app.app_context():
Expand Down Expand Up @@ -79,9 +93,10 @@ def configure_log(app):
def create_app(config):
app = Flask(__name__)
app.config.from_object(config)

register_extensions(app)
register_blueprints(app)
#configure_database(app)
# configure_database(app)
configure_oauth(app)
configure_log(app)

Expand Down
73 changes: 73 additions & 0 deletions apps/apps/data/clabs_state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"clabs": [
{
"id": "clab-sample-1",
"name": "Sample Topology",
"owner": "demo@example.com",
"status": "Running",
"created_at": "2025-09-20 12:00:00",
"nodes": 2
},
{
"lab_instance_id": "clab-20250921023818",
"user": "admin",
"title": "mano pivas",
"name": "mano pivas",
"created": "2025-09-21 02:38:18",
"status": "Running",
"nodes": 0
}
],
"details": {
"clab-sample-1": {
"lab_instance_id": "clab-sample-1",
"title": "Sample Topology",
"user": "demo@example.com",
"created": "2025-09-20 12:00:00",
"expires_at": null,
"resources": [
{
"kind": "node",
"name": "r1",
"ready": "Running",
"node_name": "linux",
"pod_ip": "172.20.20.2",
"age": "--",
"services": [],
"links": []
},
{
"kind": "node",
"name": "r2",
"ready": "Running",
"node_name": "linux",
"pod_ip": "172.20.20.3",
"age": "--",
"services": [],
"links": []
}
],
"files": []
},
"clab-20250921023818": {
"lab_instance_id": "clab-20250921023818",
"title": "mano pivas",
"user": "admin",
"created": "2025-09-21 02:38:18",
"expires_at": null,
"resources": [],
"files": [
{
"name": "teste/Novo(a) Documento de Texto.txt",
"mimetype": "text/plain",
"size": 0
},
{
"name": "teste/teste 1/Novo(a) Documento de Texto2.txt",
"mimetype": "text/plain",
"size": 0
}
]
}
}
}
11 changes: 11 additions & 0 deletions apps/clabs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- encoding: utf-8 -*-
from flask import Blueprint
from .models import Clab, ClabInstance

blueprint = Blueprint(
"clabs_blueprint",
__name__,
url_prefix="/containerlabs",
template_folder="templates",
static_folder="static",
)
97 changes: 97 additions & 0 deletions apps/clabs/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from datetime import datetime
from apps import db

class Clab(db.Model):
__tablename__ = "clabs"
__table_args__ = (
db.Index(
"ix_clabs_namespace_title_unique",
"namespace_default",
"title",
unique=True,
),
)

id = db.Column(db.String(36), primary_key=True)
title = db.Column(db.String(255), nullable=False)

description = db.Column(db.String(255))
extended_desc = db.Column(db.LargeBinary)
clab_guide_md = db.Column(db.Text)
clab_guide_html = db.Column(db.Text)
yaml_manifest = db.Column(db.Text)
namespace_default = db.Column(db.String(128))

created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)

groups = db.relationship(
"Groups",
secondary="clab_groups",
lazy="selectin",
backref=db.backref("clabs", lazy="selectin"),
)

categories = db.relationship(
"LabCategories",
secondary="clab_categories_association",
lazy="selectin",
backref=db.backref("clabs", lazy="selectin"),
)

instances = db.relationship(
"ClabInstance",
back_populates="clab",
cascade="all, delete-orphan",
lazy="selectin",
)

def __repr__(self):
return f"<Clab {self.id} {self.title}>"

class ClabInstance(db.Model):
__tablename__ = "clab_instances"

id = db.Column(db.String(24), primary_key=True)
token = db.Column(db.String(64), unique=True, index=True, nullable=False)

owner_user_id = db.Column(db.Integer, db.ForeignKey("users.id"), index=True, nullable=False)
clab_id = db.Column(db.String(36), db.ForeignKey("clabs.id"), index=True, nullable=False)

is_deleted = db.Column(db.Boolean, default=False, nullable=False)
expiration_ts = db.Column(db.DateTime, index=True)
finish_reason = db.Column(db.String(64))

_clab_resources = db.Column(db.Text)
namespace_effective = db.Column(db.String(128))

created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

clab = db.relationship(
"Clab",
back_populates="instances",
lazy="selectin",
)

owner = db.relationship(
"Users",
lazy="selectin",
)

def __repr__(self):
return f"<ClabInstance {self.id} of {self.clab_id}>"

clab_groups = db.Table(
"clab_groups",
db.Column("clab_id", db.String(36), db.ForeignKey("clabs.id"), primary_key=True),
db.Column("group_id", db.Integer, db.ForeignKey("groups.id"), primary_key=True),
)

clab_categories_association = db.Table(
"clab_categories_association",
db.Column("clab_id", db.String(36), db.ForeignKey("clabs.id"), primary_key=True),
db.Column("category_id", db.Integer, db.ForeignKey("lab_categories.id"), primary_key=True),
)
Loading