diff --git a/play-with-sld/kubernetes/k8s/sld-api-backend.yml b/play-with-sld/kubernetes/k8s/sld-api-backend.yml index f92ac18a..97af69d4 100644 --- a/play-with-sld/kubernetes/k8s/sld-api-backend.yml +++ b/play-with-sld/kubernetes/k8s/sld-api-backend.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: api-backend - image: d10s0vsky/sld-api:v3.3.1 + image: d10s0vsky/sld-api:v3.4.0 imagePullPolicy: Always command: ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] ports: diff --git a/play-with-sld/kubernetes/k8s/sld-dashboard.yml b/play-with-sld/kubernetes/k8s/sld-dashboard.yml index 29237f35..c0820bb6 100644 --- a/play-with-sld/kubernetes/k8s/sld-dashboard.yml +++ b/play-with-sld/kubernetes/k8s/sld-dashboard.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: sld-dashboard - image: d10s0vsky/sld-dashboard:v3.3.1 + image: d10s0vsky/sld-dashboard:v3.4.0 env: - name: PATH value: "/home/sld/.asdf/shims:/home/sld/.asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" diff --git a/play-with-sld/kubernetes/k8s/sld-worker-default.yml b/play-with-sld/kubernetes/k8s/sld-worker-default.yml index ce2e866c..a645f1cb 100644 --- a/play-with-sld/kubernetes/k8s/sld-worker-default.yml +++ b/play-with-sld/kubernetes/k8s/sld-worker-default.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: stack-deploy-worker-default - image: d10s0vsky/sld-api:v3.3.1 + image: d10s0vsky/sld-api:v3.4.0 imagePullPolicy: Always env: - name: TF_WARN_OUTPUT_ERRORS diff --git a/play-with-sld/kubernetes/k8s/sld-worker-squad1.yml b/play-with-sld/kubernetes/k8s/sld-worker-squad1.yml index 32f6ce83..fb22b664 100644 --- a/play-with-sld/kubernetes/k8s/sld-worker-squad1.yml +++ b/play-with-sld/kubernetes/k8s/sld-worker-squad1.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: stack-deploy-worker-squad1 - image: d10s0vsky/sld-api:v3.3.1 + image: d10s0vsky/sld-api:v3.4.0 imagePullPolicy: Always env: - name: TF_WARN_OUTPUT_ERRORS diff --git a/play-with-sld/kubernetes/k8s/sld-worker-squad2.yml b/play-with-sld/kubernetes/k8s/sld-worker-squad2.yml index 9cffde3f..f2b93cb1 100644 --- a/play-with-sld/kubernetes/k8s/sld-worker-squad2.yml +++ b/play-with-sld/kubernetes/k8s/sld-worker-squad2.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: stack-deploy-worker-squad2 - image: d10s0vsky/sld-api:v3.3.1 + image: d10s0vsky/sld-api:v3.4.0 imagePullPolicy: Always env: - name: TF_WARN_OUTPUT_ERRORS diff --git a/sld-api-backend/src/stacks/domain/entities/stacks.py b/sld-api-backend/src/stacks/domain/entities/stacks.py index a5f07893..1d040a7b 100644 --- a/sld-api-backend/src/stacks/domain/entities/stacks.py +++ b/sld-api-backend/src/stacks/domain/entities/stacks.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Literal, Dict +from typing import List, Optional, Literal from pydantic import BaseModel, Field, constr from datetime import datetime diff --git a/sld-api-backend/src/stacks/infrastructure/repositories.py b/sld-api-backend/src/stacks/infrastructure/repositories.py index 672a9ad5..04e03bc4 100644 --- a/sld-api-backend/src/stacks/infrastructure/repositories.py +++ b/sld-api-backend/src/stacks/infrastructure/repositories.py @@ -68,23 +68,14 @@ def update_stack( db_stack.tags = stack.tags db_stack.icon_path = stack.icon_path db_stack.updated_at = datetime.datetime.now() - check_None = ["string", ""] - if db_stack.stack_name not in check_None: - db_stack.stack_name = stack.stack_name - if db_stack.git_repo not in check_None: - db_stack.git_repo = stack.git_repo - if db_stack.branch not in check_None: - db_stack.branch = stack.branch - if db_stack.iac_type not in check_None: - db_stack.iac_type = stack.iac_type - if db_stack.tf_version not in check_None: - db_stack.tf_version = stack.tf_version - if db_stack.project_path not in check_None: - db_stack.project_path = stack.project_path - if db_stack.description not in check_None: - db_stack.description = stack.description - if db_stack.squad_access not in check_None: - db_stack.squad_access = squad_access + db_stack.stack_name = stack.stack_name + db_stack.git_repo = stack.git_repo + db_stack.branch = stack.branch + db_stack.iac_type = stack.iac_type + db_stack.tf_version = stack.tf_version + db_stack.project_path = stack.project_path + db_stack.description = stack.description + db_stack.squad_access = squad_access try: db.add(db_stack) db.commit() diff --git a/sld-dashboard/app/base/static/assets/css/readme.css b/sld-dashboard/app/base/static/assets/css/readme.css new file mode 100644 index 00000000..7bf1e138 --- /dev/null +++ b/sld-dashboard/app/base/static/assets/css/readme.css @@ -0,0 +1,76 @@ +table { + border-collapse: collapse; + width: 100%; +} +th, td { + border: 1px solid #020116; + padding: 8px; + text-align: left; +} +th { + background-color: #f2f2f2; +} + + +td { + word-wrap: break-word; + max-width: 300px; /* ajusta esto según tus necesidades */ +} + + +h1, h2, h3, h4, h5, h6 { + color: #333; + margin-top: 20px; +} +p { + line-height: 1.6; +} + +a { + color: #007bff; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +@media screen and (max-width: 600px) { + table { + width: 100%; + } + td { + max-width: 100px; /* ajusta para dispositivos móviles */ + } +} + +img { + max-width: 150%; + height: auto; +} + + + +/* offcanvas.css */ +.offcanvas-header { + background-color: #007bff; + color: white; +} + +.offcanvas-body { + padding: 15px; + background-color: #f8f9fa; +} + +/* Style for the button that will toggle the offcanvas */ +.offcanvas-toggle-btn { + background-color: #007bff; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.offcanvas-toggle-btn:hover { + background-color: #0056b3; +} diff --git a/sld-dashboard/app/base/static/assets/css/volt.css b/sld-dashboard/app/base/static/assets/css/volt.css index c8487a4f..b77dfec9 100644 --- a/sld-dashboard/app/base/static/assets/css/volt.css +++ b/sld-dashboard/app/base/static/assets/css/volt.css @@ -41866,3 +41866,61 @@ pre { background-size: contain; background-repeat: no-repeat; } + +.card-row { + display: flex; + justify-content: center; + flex-wrap: wrap; + align-items: center; + padding-top: 20px; +} + +.card-header { + display: flex; + align-items: center; + gap: 10px; +} + +.card-header img { + display: flex; + +} + +.card-header .title { + display: flex; + +} + +.card-columns { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 5px; +} + +/*.card-columns::before { + content: ""; + width: calc(100% - (100% / 4)); +} +*/ + +.icon-with-text { + display: flex; /* Establece el contenedor como un flexbox */ + align-items: flex-end; /* Alinea verticalmente los elementos al centro */ + gap: 35px; /* Ajusta el espacio entre la imagen y el texto */ +} + +.icon-with-text img { + margin-right: 35px; /* Añade espacio a la derecha de la imagen */ + display: flex; + justify-content: flex-end; +} + +.icon-with-text span { + display: block; /* Hace que el span se comporte como un bloque */ + line-height: 1; /* Ajusta la altura de línea si es necesario */ +} + +.gradient-background { + background: linear-gradient(to right, #ff6e7f, #bfe9ff); /* De izquierda a derecha */ +} \ No newline at end of file diff --git a/sld-dashboard/app/base/static/assets/js/selector_stacks.js b/sld-dashboard/app/base/static/assets/js/selector_stacks.js new file mode 100644 index 00000000..1799167b --- /dev/null +++ b/sld-dashboard/app/base/static/assets/js/selector_stacks.js @@ -0,0 +1,23 @@ +document.addEventListener('DOMContentLoaded', function() { + let preferredView = localStorage.getItem('preferredView'); + + // Asegúrate de que estas rutas coincidan exactamente con la ubicación de tus archivos HTML + const cardsPath = '/stacks-cards'; + const tablePath = '/stacks-list'; + + if (preferredView === 'table' && !window.location.href.endsWith(tablePath)) { + window.location.href = tablePath; + } else if (preferredView === 'cards' && !window.location.href.endsWith(cardsPath)) { + window.location.href = cardsPath; + } +}); + + +function changeView(view) { + localStorage.setItem('preferredView', view); + if (view === 'table') { + window.location.href = '/stacks-list'; + } else { + window.location.href = '/stacks-cards'; + } +} diff --git a/sld-dashboard/app/helpers/parsers.py b/sld-dashboard/app/helpers/parsers.py new file mode 100644 index 00000000..931669c3 --- /dev/null +++ b/sld-dashboard/app/helpers/parsers.py @@ -0,0 +1,21 @@ +import urllib.parse + + +def fetch_url_readme(git_repo, branch='main'): + if not git_repo.endswith('.git'): + git_repo += '.git' + + parsed_url = urllib.parse.urlparse(git_repo) + + if parsed_url.netloc == 'github.com': + raw_url = f"https://raw.githubusercontent.com/{parsed_url.path[1:-4]}" + url_readme = f"{raw_url}/{branch}/README.md" + elif parsed_url.netloc == 'gitlab.com': + raw_url = f"https://gitlab.com/{parsed_url.path[1:-4]}" + url_readme = f"{raw_url}/-/raw/{branch}/README.md" + else: + print("Unsupported Git repository platform.") + return None + + print(f"Fetching {url_readme}...") + return url_readme diff --git a/sld-dashboard/app/home/forms.py b/sld-dashboard/app/home/forms.py index efa41e9e..28289f50 100644 --- a/sld-dashboard/app/home/forms.py +++ b/sld-dashboard/app/home/forms.py @@ -40,7 +40,7 @@ class StackForm(FlaskForm): ) iac_type = SelectField( "IaC Type", - choices=[('', 'Select an IaC Type'), ('terraform', 'Terraform'), ('openTofu', 'openTofu'), ('terragrunt', 'TerraGrunt')], + choices=[('', 'Select an IaC Type'), ('terraform', 'Terraform'), ('tofu', 'openTofu'), ('terragrunt', 'TerraGrunt')], validators=[validators.DataRequired()], coerce=lambda x: 'tofu' if x == 'openTofu' else x ) @@ -58,7 +58,7 @@ class StackForm(FlaskForm): description = StringField( "Description", [ - validators.length(min=1, max=50, message="Set short Description"), + validators.length(min=30, max=60, message="Set short Description"), ], ) squad_access_edit = StringField( diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index 6d1d9610..7ed0e5ab 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -6,6 +6,10 @@ import logging from flask import jsonify, render_template, request, url_for, redirect, flash, Response +import requests +import mistletoe +from app.helpers.parsers import fetch_url_readme + import redis from app import login_manager @@ -917,6 +921,71 @@ def edit_stack(stack_id): ) stack = response.get("json") + # When user push data with POST verb + if request.method == "POST": + # Data dend to deploy + squad_acces_form = form.squad_access_edit.data + squad_acces_form_to_list = squad_acces_form.split(",") + update_stack = { + "stack_name": form.name.data.replace(" ",""), + "git_repo": form.git.data.replace(" ",""), + "branch": form.branch.data.replace(" ",""), + "squad_access": squad_acces_form_to_list, + "iac_type": form.iac_type.data, + "tf_version": form.tf_version.data.replace(" ",""), + "project_path": form.project_path.data.replace(" ",""), + "description": form.description.data, + "icon_path": request.form.get("icon_path"), + } + # Deploy + preferred_view = request.form.get('preferredView') + + response = request_url( + verb="PATCH", + uri=f"{endpoint}", + headers={"Authorization": f"Bearer {token}"}, + json=update_stack, + ) + if response.get("status_code") == 200: + flash("Updating Stack") + else: + flash(response.get("json").get("detail"), "error") + + if preferred_view == 'cards': + return redirect(url_for("home_blueprint.route_template", template="stacks-cards")) + else: + return redirect(url_for("home_blueprint.route_template", template="stacks-list")) + + return render_template( + "stack-edit.html", name="Edit Stack", form=form, stack=stack, icons=icons + ) + except ValueError: + return redirect(url_for("base_blueprint.logout")) + +@blueprint.route("/details-stack", methods=["GET", "POST"], defaults={"stack_id": None}) +@blueprint.route("/details-stack/", methods=["GET", "POST"]) +@login_required +def details_stack(stack_id): + try: + icons = list_icons() + token = decrypt(r.get(current_user.id)) + # Check if token no expired + check_unauthorized_token(token) + form = StackForm(request.form) + endpoint = f"stacks/{stack_id}" + # Get deploy data vars and set var for render + response = request_url( + verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} + ) + stack = response.get("json") + readme_url = fetch_url_readme(stack["git_repo"], stack["branch"]) + response = requests.get(readme_url) + if response.status_code == 200: + readme_content = response.text + readme_html = mistletoe.markdown(readme_content) # Convierte el contenido de Markdown a HTML + else: + readme_html = "

Error loading the README.md file, check that it exists in the repository and on the selected branch

" + # When user push data with POST verb if request.method == "POST": # Data dend to deploy @@ -949,18 +1018,18 @@ def edit_stack(stack_id): ) return render_template( - "stack-edit.html", name="Edit Stack", form=form, stack=stack, icons=icons + "stack-details.html", name="Edit Stack", form=form, stack=stack, icons=icons, readme_html=readme_html ) except ValueError: return redirect(url_for("base_blueprint.logout")) -@blueprint.route("/stack/delete/") +@blueprint.route("/stack/delete//") @login_required -def delete_stack(stack_name): +def delete_stack(view_mode, stack_name): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired + # Check if token not expired check_unauthorized_token(token) response = request_url( verb="DELETE", @@ -972,19 +1041,27 @@ def delete_stack(stack_name): flash(f"Stack {result}") else: flash(response["json"]["detail"], "error") - return redirect( - url_for("home_blueprint.route_template", template="stacks-list") - ) + + # Redirección basada en el parámetro 'view_mode' + if view_mode == "table": + return redirect(url_for("home_blueprint.route_template", template="stacks-list")) + elif view_mode == "cards": + return redirect(url_for("home_blueprint.route_template", template="stacks-cards")) + else: + flash("Invalid view mode", "error") + return redirect(url_for("home_blueprint.route_template", template="stacks-list")) + except ValueError: return redirect(url_for("base_blueprint.logout")) -@blueprint.route("/stack/resync/") + +@blueprint.route("/stack/resync//") @login_required -def resync_stack(stack_id): +def resync_stack(view_mode, stack_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired + # Check if token not expired check_unauthorized_token(token) endpoint = f"stacks/{stack_id}" # Get deploy data vars and set var for render @@ -1000,6 +1077,7 @@ def resync_stack(stack_id): "squad_access": response.get("json").get("squad_access"), "iac_type": response.get("json").get("iac_type"), "tf_version": response.get("json").get("tf_version"), + "icon_path": response.get("json").get("icon_path"), "project_path": response.get("json").get("project_path"), "description": response.get("json").get("description"), } @@ -1010,14 +1088,21 @@ def resync_stack(stack_id): json=update_stack, ) if response.get("status_code") == 200: - flash(f"Updating Stack") + flash("Updating Stack") else: flash(response.get("json").get("detail"), "error") else: flash(response.get("json").get("detail"), "error") - return redirect( - url_for("home_blueprint.route_template", template="stacks-list") - ) + + # Redirección basada en el parámetro 'view_mode' + if view_mode == "table": + return redirect(url_for("home_blueprint.route_template", template="stacks-list")) + elif view_mode == "cards": + return redirect(url_for("home_blueprint.route_template", template="stacks-cards")) + else: + flash("Invalid view mode", "error") + return redirect(url_for("home_blueprint.route_template", template="stacks-list")) + except ValueError: return redirect(url_for("base_blueprint.logout")) diff --git a/sld-dashboard/app/home/templates/deploys-list.html b/sld-dashboard/app/home/templates/deploys-list.html index 8d31fb35..5ba77b90 100644 --- a/sld-dashboard/app/home/templates/deploys-list.html +++ b/sld-dashboard/app/home/templates/deploys-list.html @@ -65,6 +65,9 @@

All Deploys

+
+ +
diff --git a/sld-dashboard/app/home/templates/stack-details.html b/sld-dashboard/app/home/templates/stack-details.html new file mode 100644 index 00000000..14bc1ad7 --- /dev/null +++ b/sld-dashboard/app/home/templates/stack-details.html @@ -0,0 +1,98 @@ +{% extends "layouts/base.html" %} +{% from 'helpers/_forms.html' import render_field %} + +{% block title %} Edit Stack {% endblock %} + + +{% block stylesheets %} + + +{% endblock stylesheets %} +{% block content %} +
+ + {% include 'includes/navigation.html' %} + +
+ +
+
+

Stack Details

+
+ +
+
+ + + +
+
+
+

Details

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue
Name{{ stack.stack_name }}
Branch{{ stack.branch }}
Iac Type{{ stack.iac_type }}
IaC Version{{ stack.tf_version }}
Description {{ stack.description }}
Squad access{{ stack.squad_access }}
+ + + {{ readme_html | safe }} + +
+
+
+ + + {% include 'includes/footer.html' %} + +
+ +{% endblock content %} + + +{% block javascripts %} +{% endblock javascripts %} diff --git a/sld-dashboard/app/home/templates/stack-edit.html b/sld-dashboard/app/home/templates/stack-edit.html index 195b6561..7af95d1c 100644 --- a/sld-dashboard/app/home/templates/stack-edit.html +++ b/sld-dashboard/app/home/templates/stack-edit.html @@ -86,8 +86,11 @@

{{stack.stack_name}}

+
+ + +
-
@@ -109,47 +112,36 @@

{{stack.stack_name}}

{% block javascripts %} + - - - - {% endblock javascripts %} + diff --git a/sld-dashboard/app/home/templates/stacks-cards.html b/sld-dashboard/app/home/templates/stacks-cards.html new file mode 100644 index 00000000..65f7360c --- /dev/null +++ b/sld-dashboard/app/home/templates/stacks-cards.html @@ -0,0 +1,221 @@ +{% extends "layouts/base.html" %} + +{% block title %} Stacks {% endblock %} + + +{% block stylesheets %} + +{% endblock stylesheets %} + +{% block content %} + +
+ + {% include 'includes/navigation.html' %} +
+ Volt logo +
+ +
+
+ +

All Stacks

+

List all stacks for deploy

+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ + + +
+
+ {% for stack in stacks %} + {% if "aws" in stack.stack_name or "azure" in stack.stack_name or "gcp" in stack.stack_name or "custom" in stack.stack_name %} + +
+
+ {% if stack.icon_path %} + Icono + {% else %} + Icono + {% endif %} +
{{stack.stack_name}}
+
+
+

{{stack.description}}

+
+
+
+ Repo: + {{ stack.git_repo.split('/')[-1] | replace('.git', '') }} + + + +
+
+ Branch: + + {{ stack.branch }} + +
+
+ + + +
+ + {% if "yoda" in current_user.role or "darth_vader" in current_user.role %} +
+ + +
+ + + + + {% endif %} + +
+ Icono +
+
+ {% endif %} + {% endfor %} +
+
+ + + + {% include 'includes/footer.html' %} + +
+ +{% endblock content %} + + +{% block javascripts %} + + + + + + +{% endblock javascripts %} diff --git a/sld-dashboard/app/home/templates/stacks-list.html b/sld-dashboard/app/home/templates/stacks-list.html index f9298ccd..791e7502 100644 --- a/sld-dashboard/app/home/templates/stacks-list.html +++ b/sld-dashboard/app/home/templates/stacks-list.html @@ -28,18 +28,26 @@

All Stacks

+
+ +
+
+ +
- +
-
+
@@ -143,6 +151,9 @@

All Stacks

@@ -185,7 +194,7 @@

All users

+
+ +
diff --git a/sld-dashboard/requirements.txt b/sld-dashboard/requirements.txt index c9b87de5..6261fab5 100644 --- a/sld-dashboard/requirements.txt +++ b/sld-dashboard/requirements.txt @@ -14,18 +14,25 @@ dnspython==2.4.2 email-validator==2.0.0.post2 fernet==1.0.1 Flask==2.3.3 +Flask-Cors==4.0.0 Flask-Login==0.6.2 Flask-Migrate==4.0.5 Flask-MySQLdb==2.0.0 Flask-SQLAlchemy==3.1.1 +Flask-SSE==1.0.0 Flask-WTF==1.2.1 +gevent==23.9.1 greenlet==3.0.0 gunicorn==21.2.0 idna==3.4 itsdangerous==2.1.2 Jinja2==3.1.2 Mako==1.2.4 +Markdown==3.5.1 +markdown2==2.4.12 +marko==2.0.2 MarkupSafe==2.1.3 +mistletoe==1.2.1 mysqlclient==2.2.0 packaging==23.2 passlib==1.7.4 @@ -48,3 +55,5 @@ urllib3==2.0.7 Werkzeug==2.3.7 wrapt==1.15.0 WTForms==3.1.0 +zope.event==5.0 +zope.interface==6.1
+ + + {% if "yoda" in current_user.role or "darth_vader" in current_user.role %}
- -