From e15cc93d0460452e6f3496228415993f29c16ed6 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Fri, 5 Aug 2022 04:17:30 +0100 Subject: [PATCH 01/16] Prepare 4.0.9 release base --- CHANGELOG.md | 11 +++++++++++ README.md | 4 ++-- app/config/version.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e462a61..ecf41452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## --- [4.0.9] - 2022/TBD +### New features +TBD +### Bug fixes +TBD +### Tweaks +TBD +### Lang +TBD +

+ ## --- [4.0.8] - 2022/08/05 ### New features - Add Crafty Version Check and notification ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/411)) diff --git a/README.md b/README.md index 021d6f3f..9d88505a 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org) -[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.8--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases) +[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.9--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases) [![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4) [![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master) -# Crafty Controller 4.0.8-beta +# Crafty Controller 4.0.9-beta > Python based Control Panel for your Minecraft Server ## What is Crafty Controller? diff --git a/app/config/version.json b/app/config/version.json index 1d4ec0c1..02c1e04c 100644 --- a/app/config/version.json +++ b/app/config/version.json @@ -1,6 +1,6 @@ { "major": 4, "minor": 0, - "sub": 8, + "sub": 9, "meta": "beta" } From ad318296dc93beb5533fcd13066440df9f9e799a Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Thu, 4 Aug 2022 23:57:22 -0400 Subject: [PATCH 02/16] Fix no new_job error --- app/classes/shared/tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 484709e4..260ceaa8 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -208,6 +208,7 @@ class TasksManager: # load schedules from DB for schedule in schedules: if schedule.interval != "reaction": + new_job = "error" if schedule.cron_string != "": try: new_job = self.scheduler.add_job( From e0377d4b2fa3bb048b4b7c9d8b12ea37f7fc27db Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 5 Aug 2022 11:56:41 -0400 Subject: [PATCH 03/16] Add translations to schedules page --- .../templates/panel/server_schedule_edit.html | 408 ++++++++++-------- .../templates/panel/server_schedules.html | 79 ++-- app/translations/en_EN.json | 18 + 3 files changed, 295 insertions(+), 210 deletions(-) diff --git a/app/frontend/templates/panel/server_schedule_edit.html b/app/frontend/templates/panel/server_schedule_edit.html index ae4e54e3..a084d53a 100644 --- a/app/frontend/templates/panel/server_schedule_edit.html +++ b/app/frontend/templates/panel/server_schedule_edit.html @@ -9,14 +9,15 @@
- +
@@ -24,159 +25,206 @@
- {% include "parts/details_stats.html" %} + {% include "parts/details_stats.html" %}
- {% include "parts/server_controls_list.html" %} + {% include "parts/server_controls_list.html" %} -
-
- {% if data['new_schedule'] == True %} -
- {% else %} - +
+
+ {% if data['new_schedule'] == True %} + + {% else %} + {% end %} - {% raw xsrf_form_html() %} - - + {% raw xsrf_form_html() %} + + -
-
- -
-
-
- -
-
-
- - -
-
- -
-
-
- - -
-
-
- +
+

{{ translate('serverScheduleConfig', 'children' , data['lang']) }}

+ +
+
+ + +
+ + {% end %} - -
- - -{% end %} - -{% block js %} - + window.onload(startup()) + -{% end %} + {% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/server_schedules.html b/app/frontend/templates/panel/server_schedules.html index 0898d881..1afc3b40 100644 --- a/app/frontend/templates/panel/server_schedules.html +++ b/app/frontend/templates/panel/server_schedules.html @@ -44,7 +44,8 @@
-

Scheduled Tasks

+

{{ translate('serverSchedules', + 'scheduledTasks', data['lang']) }}

{% if data['user_data']['hints'] %}
+ class="btn btn-info">{{ translate('serverSchedules', 'create', data['lang']) }}
- - - - - - - + + + + + + + @@ -82,7 +91,7 @@ - - - + + + @@ -137,11 +149,11 @@ @@ -152,7 +164,8 @@
IDActionCommandIntervalNext RunEnabledEdit{{ translate('serverSchedules', 'name', data['lang']) }} + {{ translate('serverSchedules', 'action', data['lang']) + }}{{ translate('serverSchedules', 'command', + data['lang']) }}{{ translate('serverSchedules', 'interval', + data['lang']) }}{{ translate('serverSchedules', 'nextRun', + data['lang']) }}{{ translate('serverSchedules', 'enabled', + data['lang']) }}{{ translate('serverSchedules', 'edit', data['lang']) + }}
{% if schedule.interval != '' %} -

Every

+

{{ translate('serverSchedules', 'every', data['lang']) }}

{{schedule.interval}} {{schedule.interval_type}}

{% elif schedule.interval_type == 'reaction' %}

{{schedule.interval_type}}

child of ID: {{ schedule.parent }}

@@ -120,9 +129,12 @@ style="table-layout:fixed;">
ActionCommandEnabled{{ translate('serverSchedules', 'action', data['lang']) + }}{{ translate('serverSchedules', 'command', + data['lang']) }}{{ translate('serverSchedules', 'enabled', + data['lang']) }}
{% if schedule.enabled %} - Yes + {{ translate('serverSchedules', 'yes', data['lang']) }} {% else %} - No + {{ translate('serverSchedules', 'no', data['lang']) }} {% end %}
-

{{schedule.schedule_id}}

+

{{schedule.name}}

{{schedule.action}}

@@ -94,7 +94,8 @@

{{ translate('serverSchedules', 'every', data['lang']) }}

{{schedule.interval}} {{schedule.interval_type}}

{% elif schedule.interval_type == 'reaction' %} -

{{schedule.interval_type}}

child of ID: {{ schedule.parent }}

+

{{schedule.interval_type}}

{{ translate('serverSchedules', 'child', data['lang'])}}: + {{ schedule.parent }}

{% else %}

Cron String:

{{schedule.cron_string}}

@@ -201,7 +202,11 @@
  • {{ translate('serverSchedules', 'nextRun', data['lang']) }}

    + {% if schedule.next_run %}

    {{schedule.next_run}}

    + {% else %} +

    zzzzzzz

    + {% end %}
  • @@ -322,7 +327,7 @@ $(document).ready(function () { console.log('ready for JS!') $('#schedule_table').DataTable({ - 'order': [4, 'desc'] + 'order': [4, 'asc'], } ); @@ -330,7 +335,7 @@ $(document).ready(function () { console.log('ready for JS!') $('#mini_schedule_table').DataTable({ - 'order': [2, 'desc'] + 'order': [2, 'asc'] } ); document.getElementById('mini_schedule_table_wrapper').hidden = true; diff --git a/app/migrations/20220805_schedule_rename_comment.py b/app/migrations/20220805_schedule_rename_comment.py new file mode 100644 index 00000000..5cd70739 --- /dev/null +++ b/app/migrations/20220805_schedule_rename_comment.py @@ -0,0 +1,17 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.rename_column("schedules", "comment", "name") + + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.rename_column("schedules", "name", "comment") + """ + Write your rollback migrations here. + """ diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 7f25d93b..4a63f4cd 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -433,7 +433,7 @@ "no": "No", "cron": "Crong String", "details": "Schedule Details", - "child": "Child of schedule named: ", + "child": "Child of schedule named ", "areYouSure": "Delete Scheduled Task?", "close": "Close", "delete": "Delete", From 3c8614f3788f2188a69fa490fe309e012fdc1ab4 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 5 Aug 2022 16:01:26 -0400 Subject: [PATCH 05/16] Fix traceback when editing schedule --- app/classes/web/panel_handler.py | 1 + app/translations/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index d70f98c0..8b66b12b 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -901,6 +901,7 @@ class PanelHandler(BaseHandler): page_data["new_schedule"] = True page_data["schedule"] = {} page_data["schedule"]["children"] = [] + page_data["schedule"]["name"] = "" page_data["schedule"]["server_id"] = server_id page_data["schedule"]["schedule_id"] = "" page_data["schedule"]["action"] = "" diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 4a63f4cd..7afe934f 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -433,7 +433,7 @@ "no": "No", "cron": "Crong String", "details": "Schedule Details", - "child": "Child of schedule named ", + "child": "Child of schedule with ID ", "areYouSure": "Delete Scheduled Task?", "close": "Close", "delete": "Delete", From b039eec857dcfb65a6c7a234c25aa20f9001adfc Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 5 Aug 2022 17:56:44 -0400 Subject: [PATCH 06/16] First commit for unloaded server. Working Frontend --- app/classes/controllers/servers_controller.py | 2 + app/classes/web/panel_handler.py | 1 + app/frontend/templates/panel/dashboard.html | 101 ++++++++++-------- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 0e5f3f5f..ba9803c4 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -146,6 +146,7 @@ class ServersController(metaclass=Singleton): def init_all_servers(self): servers = self.get_all_defined_servers() + self.failed_servers = [] for server in servers: server_id = server.get("server_id") @@ -169,6 +170,7 @@ class ServersController(metaclass=Singleton): f"{server['server_name']} at path {server['path']}. " f"Skipping this server" ) + self.failed_servers.append(server) continue temp_server_dict = { diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index b5a07eb3..00cbbd83 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -287,6 +287,7 @@ class PanelHandler(BaseHandler): "update_available": self.helper.update_available, "serverTZ": tz, "version_data": self.helper.get_version_string(), + "failed_servers": self.controller.servers.failed_servers, "user_data": exec_user, "user_role": exec_user_role, "user_crafty_permissions": exec_user_crafty_permissions, diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index 00aee7e1..ec5a21db 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -60,12 +60,12 @@

    + title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }}
    {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }}
    {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}"> {{ translate('dashboard', 'cpuUsage', data['lang']) }}: {{ data.get('hosts_data').get('cpu_usage') }}

    + title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}"> {{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_percent') }}%

    @@ -116,8 +116,8 @@ {% if len(data['servers']) > 0 %} {% if data['user_data']['hints'] %} + data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , + data-placement="top"> {% end %} {% end %}
    - {% if len(data['servers']) == 0%} + {% if len(data['servers']) == 0 and len(data['failed_servers']) == 0 %}

    {{ translate('dashboard', 'welcome', data['lang']) }}


    @@ -134,7 +134,7 @@
    {% end %} - {% if len(data['servers']) > 0 %} + {% if len(data['servers']) > 0 or len(data['failed_servers']) > 0 %}
    @@ -156,7 +156,7 @@ {% if server['alert'] %} + href="/panel/server_detail?id={{server['server_data']['server_id']}}"> {{ server['server_data']['server_name'] }}  {% else %} @@ -170,17 +170,17 @@ {% if server['user_command_permission'] %} {% if server['stats']['running'] %} + title="{{ translate('dashboard', 'stop' , data['lang']) }}">   + title="{{ translate('dashboard', 'restart' , data['lang']) }}">   + title="{{ translate('dashboard', 'kill' , data['lang']) }}">   @@ -199,15 +199,15 @@ data['lang']) }} {% else %} + title="{{ translate('dashboard', 'start' , data['lang']) }}">   + title="{{ translate('dashboard', 'clone' , data['lang']) }}">   + title="{{ translate('dashboard', 'kill' , data['lang']) }}">   {% end %} @@ -216,7 +216,7 @@ + data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"> + + {% end %} + {% for server in data['failed_servers'] %} + + + + + + + + {% end %} @@ -306,27 +319,27 @@
    + class="btn btn-link d-flex justify-content-start" type="button"> {% if server['stats']['running'] %} {{ translate('dashboard', 'online', data['lang']) }} @@ -348,22 +361,22 @@
    + aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
    {{ translate('dashboard', 'cpuUsage', data['lang']) }}
    + title="{{server['stats']['cpu']}}">
    + aria-valuemax="100">
    {{server['stats']['cpu']}}%
    @@ -452,7 +465,7 @@
    {{ translate('dashboard', 'memUsage', data['lang']) }}
    + title="{{server['stats']['mem']}}">
    + aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['mem_percent']}}% - @@ -492,7 +505,7 @@ {% if server['stats']['desc'] != 'False' %}
    + style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;"> {{ server['stats']['desc'] }}

    {% end %} From 13a0500e861b1d9eb0711684fdf5cf3df8d68cd2 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Fri, 5 Aug 2022 23:38:32 +0100 Subject: [PATCH 07/16] Switch to v2 credits schema --- app/classes/web/panel_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index b5a07eb3..cd9590d0 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -368,7 +368,7 @@ class PanelHandler(BaseHandler): ) as credits_default_local: try: remote = requests.get( - "https://craftycontrol.com/credits", allow_redirects=True + "https://craftycontrol.com/credits-v2", allow_redirects=True ) credits_dict: dict = remote.json() if not credits_dict["staff"]: From 0af2def7b05f17c8738586dd9b87f4dad50fc1d2 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Fri, 5 Aug 2022 23:39:31 +0100 Subject: [PATCH 08/16] Update credits panel to handle v2 schema --- app/frontend/templates/panel/credits.html | 6 +++++- app/translations/en_EN.json | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/frontend/templates/panel/credits.html b/app/frontend/templates/panel/credits.html index 89b3678e..f10507fa 100644 --- a/app/frontend/templates/panel/credits.html +++ b/app/frontend/templates/panel/credits.html @@ -342,7 +342,11 @@
    diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 041e4615..d8ca9a16 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -48,7 +48,7 @@ "subscriptionLevel": "Level", "supportTeam": "Support and Documentation Team", "thankYou": "THANK YOU", - "translationDesc": "to our community who translate!", + "translationDesc": "to our community who translate! [ Active = 🟒 Inactive/Retired = βšͺ ]", "translationName": "Name", "translationTitle": "Language Translation", "translator": "Translators" @@ -540,4 +540,4 @@ "userSettings": "User Settings", "uses": "Number of uses allowed (-1==No Limit)" } -} \ No newline at end of file +} From d3a6228d7e68da813bb9e7420223b72ee00b850e Mon Sep 17 00:00:00 2001 From: Zedifus Date: Fri, 5 Aug 2022 23:57:54 +0100 Subject: [PATCH 09/16] Update offline credits.json to schema v2 Also remove non-perm staff & update promoted staff --- app/config/credits.json | 352 +++++++++++++++++++++++++--------------- 1 file changed, 225 insertions(+), 127 deletions(-) diff --git a/app/config/credits.json b/app/config/credits.json index 9417c784..b62c3690 100644 --- a/app/config/credits.json +++ b/app/config/credits.json @@ -1,134 +1,232 @@ { "staff": { - "development": [ - { - "name": "Pita Bread", - "title": "CEO", - "loc": "Midwest, USA", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/craftbreadth" ], "Crafty Leadership" ], - "blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.", - "pic": "/static/assets/images/credits/pita.png" - }, - { - "name": "Xithical", - "title": "CFO/COO", - "loc": "Midwest, USA", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/xithical" ], "Crafty Leadership" ], - "blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.", - "pic": "/static/assets/images/credits/xithical.png" - }, - { - "name": "Phil Tarrant", - "title": "Creator", - "loc": "Southeast, USA", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/Ptarrant1" ], "Crafty Advisor" ], - "blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.", - "pic": "/static/assets/images/credits/ptarrant.png" - }, - { - "name": "macgeek", - "title": "Lead Software Engineer", - "loc": "Midwest, USA", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/computergeek125" ], "Crafty Advisor" ], - "blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.", - "pic": "/static/assets/images/credits/macgeek.png" - }, - { - "name": "parzivaldewey", - "title": "Support Manager", - "loc": "East Coast, USA", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/amcmanu3" ], "Support Manager" ], - "blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.", - "pic": "/static/assets/images/credits/parzivaldewey.png" - }, - { - "name": "Silversthorn", - "title": "Software Engineer", - "loc": "Provence, FR", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/Silversthorn" ], null ], - "blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"", - "pic": "/static/assets/images/credits/silversthorn.png" - }, - { - "name": "ThatOneLukas", - "title": "Software Engineer", - "loc": "Helsinki, FI", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/LukasDoesDev" ], null ], - "blurb": "Arch Linux enthusiast who likes 80s/90s music, Rust (The programming language) and light distros like Arch Linux btw. Dislikes C, C++, Go, and Microsoft. Also doesn't like Finnish weather that freezes molten snow to (deadly) ice.", - "pic": "/static/assets/images/credits/lukas.png" - }, - { - "name": "Zedifus", - "title": "DevOps Engineer", - "loc": "Scotland, UK", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/Zedifus" ], "DevOps" ], - "blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.", - "pic": "/static/assets/images/credits/zedifus.jpg" - } - ], - "support": [ - { - "name": "iSilverfyre", - "title": "Community Manager", - "loc": "South Central, US", - "tags": [ "Staff", null, "Wiki" ], - "blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.", - "pic": "/static/assets/images/credits/isilverfyre.png" - }, - { - "name": "Quentin", - "title": "Document Curator", - "loc": null, - "tags": [ "Staff", [ "Developer", "https://gitlab.com/qub3d" ], "Wiki" ], - "blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.", - "pic": "/static/assets/images/credits/qub3d.png" - }, - { - "name": "DarthLeo", - "title": "Support Engineer", - "loc": "East Coast, US", - "tags": [ "Staff", null, "Discord" ], - "blurb": "Just a simple gamer.", - "pic": "/static/assets/images/credits/darthLeo.png" - } - ], - "retired": [ - { - "name": "Kev Dagoat", - "title": "Head of Development", - "loc": "East Coast, AU", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/kevdagoat" ], "HOD" ], - "blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.", - "pic": "/static/assets/images/credits/kevdagoat.jpeg" - }, - { - "name": "Manu", - "title": null, - "loc": "Eastern, CA", - "tags": [ "Staff", "Developer", "Project Manager" ], - "blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.", - "pic": "/static/assets/images/credits/manu.png" - }, - { - "name": "UltraBlack", - "title": null, - "loc": "Bavaria, DE", - "tags": [ "Staff", null, "Idea Manager" ], - "blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.", - "pic": "/static/assets/images/credits/ultrablack.png" - }, - { - "name": "MC Gaming", - "title": null, - "loc": "Central, UK", - "tags": [ "Staff", [ "Developer", "https://gitlab.com/MCgamin1738" ], null ], - "blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.", - "pic": "/static/assets/images/credits/mcgaming.png" - } - ] + "development": [ + { + "name": "Pita Bread", + "title": "CEO", + "loc": "Midwest, USA", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/craftbreadth" + ], + "Crafty Leadership" + ], + "blurb": "Baked goods enthusiast who dabbles in Linux and sets up networks for fun. Can be found writing long-winded emails, debugging strange wifi issues, or taking care of his extensive houseplant collection.", + "pic": "/static/assets/images/credits/pita.png" + }, + { + "name": "Xithical", + "title": "CFO/COO", + "loc": "Midwest, USA", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/xithical" + ], + "Crafty Leadership" + ], + "blurb": "Having sold his soul to the IT administration gods many eons ago, you can usually find him working, going home to do work, or continuing to work in the support Discord.", + "pic": "/static/assets/images/credits/xithical.png" + }, + { + "name": "Phil Tarrant", + "title": "Creator", + "loc": "Southeast, USA", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/Ptarrant1" + ], + "Crafty Advisor" + ], + "blurb": "For best results, apply a thin layer of Phillip to code, cyber security, and parenthood for maximum effectiveness. Phillip often spends too much time with his chickens.", + "pic": "/static/assets/images/credits/ptarrant.png" + }, + { + "name": "macgeek", + "title": "Lead Software Engineer", + "loc": "Midwest, USA", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/computergeek125" + ], + "Crafty Advisor" + ], + "blurb": "Sysadmin for work and sysadmin for fun (avid homelabber). He enjoys a good tech tangent and gaming.", + "pic": "/static/assets/images/credits/macgeek.png" + }, + { + "name": "pretzeldewey πŸ₯¨", + "title": "Support Manager & Software Engineer", + "loc": "East Coast, USA", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/amcmanu3" + ], + "Crafty Advisor" + ], + "blurb": "His interests include Linux, gaming, and helping others. When he's able to unplug he enjoys biking, hiking, and playing soccer.", + "pic": "/static/assets/images/credits/parzivaldewey.png" + }, + { + "name": "Zedifus", + "title": "DevOps Manager", + "loc": "Scotland, UK", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/Zedifus" + ], + "Crafty Advisor" + ], + "blurb": "A Streamer, who is currently working in the insurance industry, self-teaching software development & DevOps, with the hopes of a career change! Enjoys playing games and has a sassy cat called Eve.", + "pic": "/static/assets/images/credits/zedifus.jpg" + }, + { + "name": "Silversthorn", + "title": "Software Engineer", + "loc": "Provence, FR", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/Silversthorn" + ], + null + ], + "blurb": "Developer at work and at home, testing his own code is a pain, so his coding precept is \"Testing is Doubting\"", + "pic": "/static/assets/images/credits/silversthorn.png" + }, + { + "name": "ThatOneLukas", + "title": "Software Engineer", + "loc": "Helsinki, FI", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/LukasDoesDev" + ], + null + ], + "blurb": "Arch Linux enthusiast who likes 80s/90s music, Rust (The programming language) and light distros like Arch Linux btw. Dislikes C, C++, Go, and Microsoft. Also doesn't like Finnish weather that freezes molten snow to (deadly) ice.", + "pic": "/static/assets/images/credits/lukas.png" + } + ], + "support": [ + { + "name": "iSilverfyre", + "title": "Community Manager", + "loc": "South Central, US", + "tags": [ + "Staff", + null, + "Wiki" + ], + "blurb": "Silver enjoys helping others with their computer needs, writing documentation, and loving her cat.", + "pic": "/static/assets/images/credits/isilverfyre.png" + }, + { + "name": "Quentin", + "title": "Document Curator", + "loc": null, + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/qub3d" + ], + "Wiki" + ], + "blurb": "Hosts Minecraft servers for his weird friends, works for an IoT company as his dayjob. The 's' in IoT stands for 'secure'.", + "pic": "/static/assets/images/credits/qub3d.png" + }, + { + "name": "Kornster", + "title": null, + "loc": "Victoria, Australia", + "tags": [ + "Discord Mod", + null, + null + ], + "blurb": "Day to day I am a Network Administrator, dealing with a range of vendors and products. Outside of work my hobbies are handyman things, such as carpentry and motor mechanics having grown up around them. My home-lab consists of Linux distros ranging from Debian, Ubuntu & Centos having self taught myself Linux for the last 12+ years.", + "pic": null + } + ], + "retired": [ + { + "name": "Kev Dagoat", + "title": "Head of Development", + "loc": "East Coast, AU", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/kevdagoat" + ], + "HOD" + ], + "blurb": "His interests include Linux, programming, and goats of course. He enjoys building APIs, K8s, and geeking over video cards.", + "pic": "/static/assets/images/credits/kevdagoat.jpeg" + }, + { + "name": "Manu", + "title": null, + "loc": "Eastern, CA", + "tags": [ + "Staff", + "Developer", + "Project Manager" + ], + "blurb": "His interests include learning, Linux, and programming. He enjoys speaking French, doing 6 things at once, and testing software.", + "pic": "/static/assets/images/credits/manu.png" + }, + { + "name": "UltraBlack", + "title": null, + "loc": "Bavaria, DE", + "tags": [ + "Staff", + null, + "Idea Manager" + ], + "blurb": "Hi, my name is Tim, and I'm a huge fan of linux. I'm often gaming and occasionally coding.", + "pic": "/static/assets/images/credits/ultrablack.png" + }, + { + "name": "MC Gaming", + "title": null, + "loc": "Central, UK", + "tags": [ + "Staff", + [ + "Developer", + "https://gitlab.com/MCgamin1738" + ], + null + ], + "blurb": "His interests include learning, Linux, and programming. He loves pentesting apps and gaming.", + "pic": "/static/assets/images/credits/mcgaming.png" + } + ] }, "translations": { - "Data Unavailable": ["🌎πŸͺ£πŸ”—πŸ‘ŽπŸ˜­"] + "Data Unavailable": [ + { + "name": "🌎πŸͺ£πŸ”—πŸ‘ŽπŸ˜­", + "status": false + } + ] }, "patrons": [ { From 98d6dd661754065302a6cbf697d847e776435c8a Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 6 Aug 2022 00:05:01 +0100 Subject: [PATCH 10/16] Update changelog !421 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecf41452..7a931dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # Changelog -## --- [4.0.9] - 2022/TBD +## --- [4.0.9] - 2022/08/06 ### New features TBD ### Bug fixes TBD ### Tweaks -TBD +- credits-v2| Translator status ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/421)) ### Lang TBD

    From 3cd190e15c5d14fa2341f63a6bbf1a70216aabd5 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 6 Aug 2022 00:30:57 +0100 Subject: [PATCH 11/16] Update changelog !419 Also add untracked changes (direct commits to dev) --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a931dea..8a7b3e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,12 @@ ### New features TBD ### Bug fixes -TBD +- Fix Schedules Traceback Bug ([Merge Request |](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419) [Commit |](https://gitlab.com/crafty-controller/crafty-4/-/commit/f69d79b7023d6c26fccb5caeae9e47b40ebe5af2) [Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/ad318296dc93beb5533fcd13066440df9f9e799a)) ### Tweaks - credits-v2| Translator status ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/421)) +- Use Names in Schedules ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419)) ### Lang -TBD +- Make Schedules panel translatable ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419))

    ## --- [4.0.8] - 2022/08/05 From 5cbb8f70185432647ed5d3fbad3192ace076584a Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 5 Aug 2022 20:04:34 -0400 Subject: [PATCH 12/16] Fix broken unloaded servers --- app/classes/controllers/servers_controller.py | 5 + app/classes/shared/main_controller.py | 8 ++ app/classes/web/ajax_handler.py | 30 +++++ app/classes/web/panel_handler.py | 98 +++++++++++++--- .../templates/panel/server_config.html | 110 ++++++++++++++---- 5 files changed, 207 insertions(+), 44 deletions(-) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index ba9803c4..6f0749b2 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -102,6 +102,11 @@ class ServersController(metaclass=Singleton): server_instance.update_server_instance() return ret + @staticmethod + def update_unloaded_server(server_obj): + ret = HelperServers.update_server(server_obj) + return ret + @staticmethod def set_download(server_id): srv = ServersController().get_server_instance_by_id(server_id) diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 393aa475..bca24fe8 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -934,6 +934,14 @@ class Controller: counter += 1 + def remove_unloaded_server(self, server_id): + try: + HelpersManagement.delete_scheduled_task_by_server(server_id) + except DoesNotExist: + logger.info("No scheduled jobs exist. Continuing.") + # remove the server from the DB + self.servers.remove_server(server_id) + @staticmethod def clear_unexecuted_commands(): HelpersManagement.clear_unexecuted_commands() diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index 42dc4275..9aa41d28 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -573,9 +573,39 @@ class AjaxHandler(BaseHandler): self.get_remote_ip(), ) + for server in self.controller.servers.failed_servers: + if server["server_id"] == int(server_id): + return self.tasks_manager.remove_all_server_tasks(server_id) self.controller.remove_server(server_id, True) + elif page == "delete_unloaded_server": + if not permissions["Config"] in user_perms: + if not superuser: + self.redirect("/panel/error?error=Unauthorized access to Config") + return + server_id = self.get_argument("id", None) + logger.info( + f"Removing server and all associated files for server: " + f"{self.controller.servers.get_server_friendly_name(server_id)}" + ) + + server_data = self.controller.servers.get_server_data_by_id(server_id) + server_name = server_data["server_name"] + + self.controller.management.add_to_audit_log( + exec_user["user_id"], + f"Deleted server {server_id} named {server_name}", + server_id, + self.get_remote_ip(), + ) + + self.tasks_manager.remove_all_server_tasks(server_id) + for item in self.controller.servers.failed_servers[:]: + if item["server_id"] == int(server_id): + self.controller.servers.failed_servers.remove(item) + self.controller.remove_unloaded_server(server_id) + def check_server_id(self, server_id, page_name): if server_id is None: logger.warning( diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 00cbbd83..e210712d 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -157,6 +157,10 @@ class PanelHandler(BaseHandler): if server_id is None: self.redirect("/panel/error?error=Invalid Server ID") return None + for server in self.controller.servers.failed_servers: + if int(server_id) == server["server_id"]: + self.failed_server = True + return server_id # Does this server exist? if not self.controller.servers.server_id_exists(server_id): self.redirect("/panel/error?error=Invalid Server ID") @@ -207,6 +211,7 @@ class PanelHandler(BaseHandler): @tornado.web.authenticated async def get(self, page): + self.failed_server = False error = self.get_argument("error", "WTF Error!") template = "panel/denied.html" @@ -504,9 +509,11 @@ class PanelHandler(BaseHandler): server_id = self.check_server_id() if server_id is None: return - - server_obj = self.controller.servers.get_server_instance_by_id(server_id) - page_data["backup_failed"] = server_obj.last_backup_status() + if not self.failed_server: + server_obj = self.controller.servers.get_server_instance_by_id( + server_id + ) + page_data["backup_failed"] = server_obj.last_backup_status() server_obj = None valid_subpages = [ @@ -518,18 +525,57 @@ class PanelHandler(BaseHandler): "admin_controls", "schedules", ] - - server = self.controller.servers.get_server_instance_by_id(server_id) + if not self.failed_server: + server = self.controller.servers.get_server_instance_by_id(server_id) # server_data isn't needed since the server_stats also pulls server data page_data["server_data"] = self.controller.servers.get_server_data_by_id( server_id ) - page_data["server_stats"] = self.controller.servers.get_server_stats_by_id( - server_id - ) - page_data["downloading"] = self.controller.servers.get_download_status( - server_id - ) + if not self.failed_server: + page_data[ + "server_stats" + ] = self.controller.servers.get_server_stats_by_id(server_id) + else: + server_temp_obj = self.controller.servers.get_server_data_by_id( + server_id + ) + page_data["server_stats"] = { + "server_id": { + "server_id": server_id, + "server_name": server_temp_obj["server_name"], + "server_uuid": server_temp_obj["server_uuid"], + "path": server_temp_obj["path"], + "log_path": server_temp_obj["log_path"], + "executable": server_temp_obj["executable"], + "execution_command": server_temp_obj["execution_command"], + "stop_command": server_temp_obj["stop_command"], + "executable_update_url": server_temp_obj[ + "executable_update_url" + ], + "auto_start_delay": server_temp_obj["auto_start_delay"], + "server_ip": server_temp_obj["server_ip"], + "server_port": server_temp_obj["server_port"], + "logs_delete_after": server_temp_obj["logs_delete_after"], + "auto_start": server_temp_obj["auto_start"], + "crash_detection": server_temp_obj["crash_detection"], + "show_status": server_temp_obj["show_status"], + }, + "running": False, + "crashed": False, + "server_type": "N/A", + "cpu": "N/A", + "mem": "N/A", + "int_ping_results": [], + "version": "N/A", + "desc": "N/A", + "started": "False", + } + if not self.failed_server: + page_data["downloading"] = self.controller.servers.get_download_status( + server_id + ) + else: + page_data["downloading"] = False page_data["server_id"] = server_id try: page_data["waiting_start"] = self.controller.servers.get_waiting_start( @@ -538,7 +584,10 @@ class PanelHandler(BaseHandler): except Exception as e: logger.error(f"Failed to get server waiting to start: {e}") page_data["waiting_start"] = False - page_data["get_players"] = server.get_server_players() + if not self.failed_server: + page_data["get_players"] = server.get_server_players() + else: + page_data["get_players"] = [] page_data["active_link"] = subpage page_data["permissions"] = { "Commands": EnumPermissionsServer.COMMANDS, @@ -555,12 +604,14 @@ class PanelHandler(BaseHandler): ] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id ) - page_data["server_stats"]["crashed"] = self.controller.servers.is_crashed( - server_id - ) - page_data["server_stats"][ - "server_type" - ] = self.controller.servers.get_server_type_by_id(server_id) + if not self.failed_server: + page_data["server_stats"][ + "crashed" + ] = self.controller.servers.is_crashed(server_id) + if not self.failed_server: + page_data["server_stats"][ + "server_type" + ] = self.controller.servers.get_server_type_by_id(server_id) if subpage not in valid_subpages: logger.debug("not a valid subpage") if not subpage: @@ -640,6 +691,7 @@ class PanelHandler(BaseHandler): return page_data["java_versions"] = Helpers.find_java_installs() server_obj: Servers = self.controller.servers.get_server_obj(server_id) + page_data["failed"] = self.failed_server page_java = [] page_data["java_versions"].append("java") for version in page_data["java_versions"]: @@ -1467,7 +1519,15 @@ class PanelHandler(BaseHandler): server_obj.auto_start = auto_start server_obj.crash_detection = crash_detection server_obj.logs_delete_after = logs_delete_after - self.controller.servers.update_server(server_obj) + failed = False + for servers in self.controller.servers.failed_servers: + if servers["server_id"] == int(server_id): + failed = True + if not failed: + self.controller.servers.update_server(server_obj) + else: + self.controller.servers.update_unloaded_server(server_obj) + self.controller.servers.init_all_servers() self.controller.servers.crash_detection(server_obj) self.controller.servers.refresh_server_settings(server_id) diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html index 9bb03a71..caf8f951 100644 --- a/app/frontend/templates/panel/server_config.html +++ b/app/frontend/templates/panel/server_config.html @@ -32,12 +32,14 @@
    + {% if not data['failed'] %} {% include "parts/server_controls_list.html %} {% include "parts/m_server_controls_list.html %} + {% end %}
    @@ -117,7 +119,6 @@

    {% end %} -
    @@ -255,19 +267,19 @@
    @@ -275,7 +287,7 @@ {% block js %} From 0642c613149713b2b6ddbb57fc647ba27618210c Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 6 Aug 2022 01:13:26 +0100 Subject: [PATCH 13/16] Update changelog !420 --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7b3e5b..68ff2c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,9 @@ # Changelog ## --- [4.0.9] - 2022/08/06 -### New features -TBD ### Bug fixes - Fix Schedules Traceback Bug ([Merge Request |](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419) [Commit |](https://gitlab.com/crafty-controller/crafty-4/-/commit/f69d79b7023d6c26fccb5caeae9e47b40ebe5af2) [Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/ad318296dc93beb5533fcd13066440df9f9e799a)) +- Fix handling of missing servers ([Merge RequestπŸŽ‰](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/420)) ### Tweaks - credits-v2| Translator status ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/421)) - Use Names in Schedules ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419)) From 8897856aef5542a4f2960af4ccc14f3448c211cc Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 5 Aug 2022 20:24:42 -0400 Subject: [PATCH 14/16] Remove Update button when unloaded Fix duplicate list issue --- app/classes/controllers/servers_controller.py | 3 ++- app/frontend/templates/panel/server_config.html | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 6f0749b2..650a16b0 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -175,7 +175,8 @@ class ServersController(metaclass=Singleton): f"{server['server_name']} at path {server['path']}. " f"Skipping this server" ) - self.failed_servers.append(server) + if server not in self.failed_servers: + self.failed_servers.append(server) continue temp_server_dict = { diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html index caf8f951..6c9a8b36 100644 --- a/app/frontend/templates/panel/server_config.html +++ b/app/frontend/templates/panel/server_config.html @@ -242,9 +242,11 @@ }}
    {{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }} {% else %} + {% if not data['failed'] %} + {% end %} {% if not data['failed'] %} From 247678e6c6af5e7d5dbfcf3bfdcec49fc5980e55 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 6 Aug 2022 01:47:25 +0100 Subject: [PATCH 15/16] Correct offline credits for kofi schema update --- app/config/credits.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/config/credits.json b/app/config/credits.json index b62c3690..ae2017d9 100644 --- a/app/config/credits.json +++ b/app/config/credits.json @@ -231,7 +231,8 @@ "patrons": [ { "name": "Data Unavailable 🌎πŸͺ£πŸ”—πŸ‘ŽπŸ˜­", - "level": "" + "level": "", + "source": "" } ], "lastUpdate": 0 From 0b5b0d2092bbb1b2d9a83674e69226c06d104d4a Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 6 Aug 2022 01:49:43 +0100 Subject: [PATCH 16/16] Update changelog bugfix-offline-credits --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68ff2c30..cc322796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug fixes - Fix Schedules Traceback Bug ([Merge Request |](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419) [Commit |](https://gitlab.com/crafty-controller/crafty-4/-/commit/f69d79b7023d6c26fccb5caeae9e47b40ebe5af2) [Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/ad318296dc93beb5533fcd13066440df9f9e799a)) - Fix handling of missing servers ([Merge RequestπŸŽ‰](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/420)) +- Fix offline credits panel stack ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/247678e6c6af5e7d5dbfcf3bfdcec49fc5980e55)) ### Tweaks - credits-v2| Translator status ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/421)) - Use Names in Schedules ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/419))
    + title="{{server['stats']['cpu']}}">
    + aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['cpu']}}%
    + title="{{server['stats']['mem']}}">
    + aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['mem_percent']}}% - @@ -263,7 +263,7 @@ {% if server['stats']['desc'] != 'False' %}
    {{ + style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">{{ server['stats']['desc'] }}

    {% end %} @@ -287,7 +287,20 @@ {% end %}
     {{server['server_name']}} +  Unloaded
    {% for language in data['translations'][person] %} - {{ language }} + {% if language['status'] %} + {{ language['name'] }} + {% else %} + {{ language['name'] }} + {% end %} {% end %}