mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
fix easy mode + refactor builder + add widget py
This commit is contained in:
parent
e8b2ba95bc
commit
ab1208d7f2
4 changed files with 475 additions and 193 deletions
171
src/ui/builder.py
Normal file
171
src/ui/builder.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import base64
|
||||
import json
|
||||
|
||||
|
||||
def stat_widget(link, containerColums, title, subtitle, subtitle_color, stat, icon_name):
|
||||
"""Return a valid format to render a Stat widget"""
|
||||
return {
|
||||
"type": "card",
|
||||
"link": link,
|
||||
"containerColumns": containerColums,
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": title,
|
||||
"subtitle": subtitle,
|
||||
"subtitleColor": subtitle_color,
|
||||
"stat": stat,
|
||||
"iconName": icon_name,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def instance_widget(containerColumns, pairs, status, title, buttons):
|
||||
"""Return a valid format to render an Instance widget"""
|
||||
return {
|
||||
"type": "card",
|
||||
"containerColumns": containerColumns,
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Instance",
|
||||
"data": {
|
||||
"pairs": pairs,
|
||||
"status": status,
|
||||
"title": title,
|
||||
"buttons": buttons,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def home_builder(data):
|
||||
"""
|
||||
It returns the needed format from data to render the home page in JSON format for the Vue.js builder
|
||||
"""
|
||||
version_card = stat_widget(
|
||||
link="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro",
|
||||
containerColums={"pc": 4, "tablet": 6, "mobile": 12},
|
||||
title="home_version",
|
||||
subtitle=(
|
||||
"home_all_features_available"
|
||||
if data.get("is_pro_version")
|
||||
else (
|
||||
"home_awaiting_compliance"
|
||||
if data.get("pro_status") == "active" and data.get("pro_overlapped")
|
||||
else (
|
||||
"home_renew_license"
|
||||
if data.get("pro_status") == "expired"
|
||||
else "home_talk_to_team" if data.get("pro_status") == "suspended" else "home_upgrade_to_pro"
|
||||
)
|
||||
)
|
||||
),
|
||||
subtitle_color="success" if data.get("is_pro_version") else "warning",
|
||||
stat=(
|
||||
"home_pro"
|
||||
if data.get("is_pro_version")
|
||||
else (
|
||||
"home_pro_locked"
|
||||
if data.get("pro_status") == "active" and data.get("pro_overlapped")
|
||||
else "home_expired" if data.get("pro_status") == "expired" else "home_suspended" if data.get("pro_status") == "suspended" else "home_free"
|
||||
)
|
||||
),
|
||||
icon_name="crown" if data.get("is_pro_version") else "key",
|
||||
)
|
||||
|
||||
version_num_card = stat_widget(
|
||||
link="https://github.com/bunkerity/bunkerweb",
|
||||
containerColums={"pc": 4, "tablet": 6, "mobile": 12},
|
||||
title="home_version_number",
|
||||
subtitle=(
|
||||
"home_couldnt_find_remote"
|
||||
if not data.get("remote_version")
|
||||
else "home_latest_version" if data.get("remote_version") and data.get("check_version") else "home_update_available"
|
||||
),
|
||||
subtitle_color=("error" if not data.get("remote_version") else "success" if data.get("remote_version") and data.get("check_version") else "warning"),
|
||||
stat=data.get("version"),
|
||||
icon_name="wire",
|
||||
)
|
||||
|
||||
instances_card = stat_widget(
|
||||
link="instances",
|
||||
containerColums={"pc": 4, "tablet": 6, "mobile": 12},
|
||||
title="home_instances",
|
||||
subtitle="home_total_number",
|
||||
subtitle_color="info",
|
||||
stat=data.get("instances_number"),
|
||||
icon_name="box",
|
||||
)
|
||||
|
||||
services_card = stat_widget(
|
||||
link="services",
|
||||
containerColums={"pc": 4, "tablet": 6, "mobile": 12},
|
||||
title="home_services",
|
||||
subtitle="home_all_methods_included",
|
||||
subtitle_color="info",
|
||||
stat=data.get("services_number"),
|
||||
icon_name="disk",
|
||||
)
|
||||
|
||||
plugins_card = stat_widget(
|
||||
link="plugins",
|
||||
containerColums={"pc": 4, "tablet": 6, "mobile": 12},
|
||||
title="home_plugins",
|
||||
subtitle="home_errors_found" if data.get("plugins_errors") > 0 else "home_no_error",
|
||||
subtitle_color="error" if data.get("plugins_errors") > 0 else "success",
|
||||
stat="42",
|
||||
icon_name="puzzle",
|
||||
)
|
||||
|
||||
builder = [version_card, version_num_card, instances_card, services_card, plugins_card]
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
||||
|
||||
def instances_builder(instances: list):
|
||||
"""
|
||||
It returns the needed format from data to render the instances page in JSON format for the Vue.js builder
|
||||
"""
|
||||
builder = []
|
||||
|
||||
for instance in instances:
|
||||
# setup actions buttons
|
||||
actions = (
|
||||
["restart", "stop"]
|
||||
if instance._type == "local" and instance.health
|
||||
else (
|
||||
["reload", "stop"]
|
||||
if not instance._type == "local" and instance.health
|
||||
else ["start"] if instance._type == "local" and not instance.health else []
|
||||
)
|
||||
)
|
||||
|
||||
buttons = [
|
||||
{
|
||||
"attrs": {
|
||||
"data-submit-form": f"""{{"INSTANCE_ID" : "{instance._id}", "operation" : "{action}" }}""",
|
||||
},
|
||||
"text": f"action_{action}",
|
||||
"color": "success" if action == "start" else "error" if action == "stop" else "warning",
|
||||
}
|
||||
for action in actions
|
||||
]
|
||||
|
||||
instance = instance_widget(
|
||||
containerColumns={"pc": 6, "tablet": 6, "mobile": 12},
|
||||
pairs=[
|
||||
{"key": "instances_hostname", "value": instance.hostname},
|
||||
{"key": "instances_type", "value": instance._type},
|
||||
{"key": "instances_status", "value": "instances_active" if instance.health else "instances_inactive"},
|
||||
],
|
||||
status="success" if instance.health else "error",
|
||||
title=instance.name,
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
builder.append(instance)
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
|
@ -8,6 +8,7 @@ import Button from "@components/Widget/Button.vue";
|
|||
import Text from "@components/Widget/Text.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useCheckPluginsValidity } from "@utils/form.js";
|
||||
import { useEasyForm } from "@store/easy.js";
|
||||
|
||||
/**
|
||||
@name Form/Easy.vue
|
||||
|
|
@ -42,6 +43,8 @@ import { useCheckPluginsValidity } from "@utils/form.js";
|
|||
@param {object} columns - Columns object.
|
||||
*/
|
||||
|
||||
const easyForm = useEasyForm();
|
||||
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
template: {
|
||||
|
|
@ -78,7 +81,7 @@ const data = reactive({
|
|||
|
||||
function setValidity() {
|
||||
const [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id] =
|
||||
useCheckPluginsValidity(data.base);
|
||||
useCheckPluginsValidity(easyForm.templateUI);
|
||||
|
||||
data.stepErr = id;
|
||||
data.isRegErr = isRegErr;
|
||||
|
|
@ -86,11 +89,6 @@ function setValidity() {
|
|||
data.settingErr = `"${settingNameErr}"`;
|
||||
}
|
||||
|
||||
function updateTemplate(e) {
|
||||
if (!e.target.closest("[data-easy-form-step]")) return;
|
||||
useUpdateTemp(e, data.base);
|
||||
}
|
||||
|
||||
const buttonSave = {
|
||||
id: uuidv4(),
|
||||
text: "action_save",
|
||||
|
|
@ -116,13 +114,15 @@ const buttonNext = {
|
|||
|
||||
onMounted(() => {
|
||||
// Restart step one every time the component is mounted
|
||||
easyForm.setTemplate(props.template);
|
||||
data.currStep = 0;
|
||||
setValidity();
|
||||
useListenTempFields(updateTemplate);
|
||||
// I want updatInp to access event, data.base and the container attribut
|
||||
easyForm.useListenTempFields();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
useUnlistenTempFields(updateTemplate);
|
||||
easyForm.useUnlistenTempFields();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ onUnmounted(() => {
|
|||
<Title type="card" :title="'dashboard_easy_mode'" />
|
||||
<Subtitle type="card" :subtitle="'dashboard_easy_mode_subtitle'" />
|
||||
|
||||
<template v-for="(step, id) in data.base">
|
||||
<template v-for="(step, id) in easyForm.templateUI">
|
||||
<Container
|
||||
data-is="content"
|
||||
data-easy-form-step
|
||||
|
|
|
|||
294
src/ui/client/dashboard/store/easy.js
Normal file
294
src/ui/client/dashboard/store/easy.js
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
/**
|
||||
@name useEasyForm
|
||||
@description Store to share the template and others form data.
|
||||
*/
|
||||
export const useEasyForm = defineStore("easy", () => {
|
||||
// Default template, don't touch it. It will be used to reset the template.
|
||||
const template = ref({});
|
||||
// Base template will keep every data (data that doesn't need to be render on UI but need to be there for backend).
|
||||
const templateBase = ref({});
|
||||
// UI template will keep the data that will be render on UI.
|
||||
const templateUI = ref({});
|
||||
// UI template will keep the data that will be render on UI with additionnal format like filtering.
|
||||
const templateUIFormat = ref({});
|
||||
const updateCount = ref(0);
|
||||
/**
|
||||
@name setTemplate
|
||||
@description Set the template we are going to use to generate the form and update it (like adding multiples).
|
||||
@param template - Template with plugins list and detail settings
|
||||
*/
|
||||
function setTemplate(template) {
|
||||
template.value = template;
|
||||
templateBase.value = template;
|
||||
templateUI.value = JSON.parse(JSON.stringify(template));
|
||||
templateUIFormat.value = JSON.parse(JSON.stringify(template));
|
||||
}
|
||||
|
||||
/**
|
||||
@name delMultiple
|
||||
@description This function will delete a group of multiples in the template.
|
||||
The way the backend is working is that to delete a group, we need to send the group name with all default values.
|
||||
This function needs to be call from the multiples component parent with the template and the group name to delete.
|
||||
We will update the values of the group to default values.
|
||||
@param pluginId - id of the plugin on the template array.
|
||||
@param multName - Input id to update
|
||||
@param groupName - Input value to update
|
||||
*/
|
||||
function delMultiple(pluginId, multName, groupName) {
|
||||
// Get the index of plugin using pluginId
|
||||
const index = templateBase.value.findIndex(
|
||||
(plugin) => plugin.id === pluginId
|
||||
);
|
||||
|
||||
// For back end, we need to keep the group but updating values to default in order to delete it
|
||||
for (const [key, value] of Object.entries(
|
||||
templateBase.value[index].multiples[multName][groupName]
|
||||
)) {
|
||||
value.value = value.default;
|
||||
}
|
||||
|
||||
// For UI, we can delete the group to avoid rendering it
|
||||
delete templateUI.value[index].multiples[multName][groupName];
|
||||
updateCount.value++;
|
||||
}
|
||||
|
||||
/**
|
||||
@name addMultiple
|
||||
@description This function will add a group of multiple in the template with default values.
|
||||
Each plugin has a key "multiples_schema" with each multiples group and their default values.
|
||||
We will retrieve the wanted multiple group and add it on the "multiples" key that contains the multiples that apply to the plugin.
|
||||
@param pluginId - id of the plugin on the template array.
|
||||
@param multName - multiple group name to add
|
||||
*/
|
||||
function addMultiple(pluginId, multName) {
|
||||
// Get the index of plugin using pluginId
|
||||
const index = templateBase.value.findIndex(
|
||||
(plugin) => plugin.id === pluginId
|
||||
);
|
||||
// Get the right multiple schema
|
||||
const multipleSchema = JSON.parse(
|
||||
JSON.stringify(templateBase.value[index]?.multiples_schema[multName])
|
||||
);
|
||||
const newMultiple = {};
|
||||
|
||||
// Get the highest id in Object.keys(plugin?.multiples[multName])
|
||||
const nextGroupId =
|
||||
Math.max(...Object.keys(templateBase.value[index]?.multiples[multName])) +
|
||||
1;
|
||||
|
||||
// Set the default values as value
|
||||
for (const [key, value] of Object.entries(multipleSchema)) {
|
||||
value.value = value.default;
|
||||
newMultiple[`${key}${nextGroupId > 0 ? "_" + nextGroupId : ""}`] = value;
|
||||
}
|
||||
// Add new group as first key of plugin.multiples.multName
|
||||
templateBase.value[index].multiples[multName][nextGroupId] = newMultiple;
|
||||
// We need to show the new group on UI too
|
||||
templateUI.value[index].multiples[multName][nextGroupId] = newMultiple;
|
||||
updateCount.value++;
|
||||
}
|
||||
|
||||
/**
|
||||
@name useListenTempFields
|
||||
@description This will add an handler to all needed event listeners to listen to input, select... fields in order to update the template settings.
|
||||
@example
|
||||
function hander(e) {
|
||||
// some code before calling useUpdateTemp
|
||||
if (!e.target.closest("[data-easy-form-plugin]")) return;
|
||||
useUpdateTemp(e, data.base);
|
||||
}
|
||||
*/
|
||||
function useListenTempFields() {
|
||||
window.addEventListener("input", useUpdateTemp);
|
||||
window.addEventListener("change", useUpdateTemp);
|
||||
window.addEventListener("click", useUpdateTemp);
|
||||
}
|
||||
|
||||
/**
|
||||
@name useUnlistenTempFields
|
||||
@description This will stop listening to input, select... fields. Performance optimization and avoid duplicate calls conflicts.
|
||||
@example
|
||||
function hander(e) {
|
||||
// some code before calling useUpdateTemp
|
||||
if (!e.target.closest("[data-easy-form-plugin]")) return;
|
||||
useUpdateTemp(e, data.base);
|
||||
}
|
||||
*/
|
||||
function useUnlistenTempFields() {
|
||||
window.removeEventListener("change", useUpdateTemp);
|
||||
window.removeEventListener("click", useUpdateTemp);
|
||||
}
|
||||
|
||||
/**
|
||||
@name useUpdateTemp
|
||||
@description This function will check if the target is a setting input-like field.
|
||||
In case it is, it will get the id and value for each field case, this will allow to update the template settings.
|
||||
@example
|
||||
template = [
|
||||
{
|
||||
name: "test",
|
||||
settings: {
|
||||
test: {
|
||||
required: true,
|
||||
value: "",
|
||||
pattern: "^[a-zA-Z0-9]*$",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@param e - Event object, get it by default in the event listener.
|
||||
@param templates - Array of templates to update
|
||||
*/
|
||||
function useUpdateTemp(e, templates) {
|
||||
templates = [templateBase.value, templateUI.value];
|
||||
// Avoid event that are not in the form
|
||||
if (!e.target.closest("[data-easy-form]")) return;
|
||||
// Avoid multiple calls on datepicker
|
||||
if (e.type === "change" && e.target.tagName === "INPUT") return;
|
||||
// Avoid multiple calls on checkbox
|
||||
if (
|
||||
e.type !== "input" &&
|
||||
e.target.tagName === "INPUT" &&
|
||||
e.target.type === "checkbox"
|
||||
)
|
||||
return;
|
||||
|
||||
// Wait some ms that previous update logic is done like datepicker
|
||||
setTimeout(() => {
|
||||
let inpId, inpValue;
|
||||
|
||||
// Case target is input (a little different for datepicker)
|
||||
if (e.target.tagName === "INPUT") {
|
||||
inpId = e.target.id;
|
||||
inpValue = e.target.hasAttribute("data-timestamp")
|
||||
? e.target.getAttribute("data-timestamp")
|
||||
: e.target.value;
|
||||
}
|
||||
|
||||
// Case target is select
|
||||
if (
|
||||
e.target.closest("[data-field-container]") &&
|
||||
e.target.hasAttribute("data-setting-id") &&
|
||||
e.target.hasAttribute("data-setting-value")
|
||||
) {
|
||||
inpId = e.target.getAttribute("data-setting-id");
|
||||
inpValue = e.target.getAttribute("data-setting-value");
|
||||
}
|
||||
|
||||
// Case target is not an input-like
|
||||
if (!inpId) return;
|
||||
|
||||
// update settings
|
||||
useUpdateTempSettings(templates, inpId, inpValue, e.target);
|
||||
useUpdateTempMultiples(templates, inpId, inpValue, e.target);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
@name useUpdateTempSettings
|
||||
@description This function will loop on template settings in order to update the setting value.
|
||||
This will check each plugin.settings (what I call regular) instead of other type of settings like multiples (in plugin.multiples).
|
||||
This function needs to be call in useUpdateTemp.
|
||||
@param templates - Templates array with plugins list and detail settings
|
||||
@param inpId - Input id to update
|
||||
@param inpValue - Input value to update
|
||||
*/
|
||||
function useUpdateTempSettings(templates, inpId, inpValue, target) {
|
||||
// Case get data-group attribut, this is not a regular setting
|
||||
if (target.closest("[data-group]")) return;
|
||||
|
||||
for (let i = 0; i < templates.length; i++) {
|
||||
const template = templates[i];
|
||||
|
||||
// Try to update settings
|
||||
let isSettingUpdated = false;
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
const plugin = template[i];
|
||||
const settings = plugin?.settings;
|
||||
if (!settings) continue;
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
if (value.id === inpId) {
|
||||
value.value = inpValue;
|
||||
isSettingUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isSettingUpdated) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@name useUpdateTempMultiples
|
||||
@description This function will loop on template multiples in order to update the setting value.
|
||||
This will check each plugin.multiples that can be found in the template.
|
||||
This function needs to be call in useUpdateTemp.
|
||||
@param templates - Templates array with plugins list and detail settings
|
||||
@param inpId - Input id to update
|
||||
@param inpValue - Input value to update
|
||||
*/
|
||||
function useUpdateTempMultiples(templates, inpId, inpValue, target) {
|
||||
// Case get data-group attribut, this is not a regular setting
|
||||
if (!target.closest("[data-group='multiple']")) return;
|
||||
const multName =
|
||||
target
|
||||
.closest("[data-group='multiple']")
|
||||
.getAttribute("data-mult-name") || "";
|
||||
const groupName =
|
||||
target
|
||||
.closest("[data-group='multiple']")
|
||||
.getAttribute("data-group-name") || "";
|
||||
|
||||
for (let i = 0; i < templates.length; i++) {
|
||||
const template = templates[i];
|
||||
// Check at the same time the inpId without prefix group he is part of
|
||||
// And try to update an existing inpId
|
||||
// Case we found the inpId, we update the value
|
||||
// Case we didn't find existing inpId, we create a new one
|
||||
let isSettingUpdated = false;
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
const plugin = template[i];
|
||||
const multiples = plugin?.multiples;
|
||||
// Case no multiples, continue
|
||||
if (!multiples || Object.keys(multiples).length <= 0) continue;
|
||||
// Check if can find mult name in multiples
|
||||
if (!(multName in multiples)) continue;
|
||||
// Check if can find group name in multiples
|
||||
if (!(groupName in multiples[multName])) continue;
|
||||
const settings = multiples[multName][groupName];
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
if (value.id !== inpId) continue;
|
||||
value.value = inpValue;
|
||||
isSettingUpdated = true;
|
||||
break;
|
||||
}
|
||||
if (isSettingUpdated) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@name $reset
|
||||
@description Will reset the template to the original one using the default template. The default template need to be set once.
|
||||
*/
|
||||
function $reset() {
|
||||
templateBase.value = template.value;
|
||||
templateUI.value = template.value;
|
||||
updateCount.value++;
|
||||
}
|
||||
|
||||
return {
|
||||
templateBase,
|
||||
templateUI,
|
||||
templateUIFormat,
|
||||
setTemplate,
|
||||
addMultiple,
|
||||
delMultiple,
|
||||
useListenTempFields,
|
||||
useUnlistenTempFields,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
185
src/ui/main.py
185
src/ui/main.py
|
|
@ -11,7 +11,7 @@ from sys import path as sys_path, modules as sys_modules
|
|||
from pathlib import Path
|
||||
from typing import Union
|
||||
from uuid import uuid4
|
||||
|
||||
from builder import home_builder, instances_builder
|
||||
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
|
||||
if deps_path not in sys_path:
|
||||
sys_path.append(deps_path)
|
||||
|
|
@ -676,135 +676,6 @@ def totp():
|
|||
return render_template("totp.html")
|
||||
|
||||
|
||||
def home_builder(data):
|
||||
"""
|
||||
It returns the home page in JSON format for the Vue.js builder
|
||||
"""
|
||||
|
||||
version_card = {
|
||||
"type": "card",
|
||||
"link": "https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro",
|
||||
"containerColumns": {"pc": 4, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": "home_version",
|
||||
"subtitle": (
|
||||
"home_all_features_available"
|
||||
if data.get("is_pro_version")
|
||||
else (
|
||||
"home_awaiting_compliance"
|
||||
if data.get("pro_status") == "active" and data.get("pro_overlapped")
|
||||
else (
|
||||
"home_renew_license"
|
||||
if data.get("pro_status") == "expired"
|
||||
else "home_talk_to_team" if data.get("pro_status") == "suspended" else "home_upgrade_to_pro"
|
||||
)
|
||||
)
|
||||
),
|
||||
"subtitleColor": "success" if data.get("is_pro_version") else "warning",
|
||||
"stat": (
|
||||
"home_pro"
|
||||
if data.get("is_pro_version")
|
||||
else (
|
||||
"home_pro_locked"
|
||||
if data.get("pro_status") == "active" and data.get("pro_overlapped")
|
||||
else (
|
||||
"home_expired"
|
||||
if data.get("pro_status") == "expired"
|
||||
else "home_suspended" if data.get("pro_status") == "suspended" else "home_free"
|
||||
)
|
||||
)
|
||||
),
|
||||
"iconName": "crown" if data.get("is_pro_version") else "key",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
version_num_card = {
|
||||
"type": "card",
|
||||
"link": "https://github.com/bunkerity/bunkerweb",
|
||||
"containerColumns": {"pc": 4, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": "home_version_number",
|
||||
"subtitle": (
|
||||
"home_couldnt_find_remote"
|
||||
if not data.get("remote_version")
|
||||
else "home_latest_version" if data.get("remote_version") and data.get("check_version") else "home_update_available"
|
||||
),
|
||||
"subtitleColor": (
|
||||
"error" if not data.get("remote_version") else "success" if data.get("remote_version") and data.get("check_version") else "warning"
|
||||
),
|
||||
"stat": data.get("version"),
|
||||
"iconName": "wire",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
instances_card = {
|
||||
"type": "card",
|
||||
"link": "instances",
|
||||
"containerColumns": {"pc": 4, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": "home_instances",
|
||||
"subtitle": "home_total_number",
|
||||
"subtitleColor": "info",
|
||||
"stat": data.get("instances_number"),
|
||||
"iconName": "box",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
services_card = {
|
||||
"type": "card",
|
||||
"link": "services",
|
||||
"containerColumns": {"pc": 4, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": "home_services",
|
||||
"subtitle": "home_all_methods_included",
|
||||
"subtitleColor": "info",
|
||||
"stat": data.get("services_number"),
|
||||
"iconName": "disk",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
plugins_card = {
|
||||
"type": "card",
|
||||
"link": "plugins",
|
||||
"containerColumns": {"pc": 4, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Stat",
|
||||
"data": {
|
||||
"title": "home_plugins",
|
||||
"subtitle": "home_errors_found" if data.get("plugins_errors") > 0 else "home_no_error",
|
||||
"subtitleColor": "error" if data.get("plugins_errors") > 0 else "success",
|
||||
"stat": "42",
|
||||
"iconName": "puzzle",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
builder = [version_card, version_num_card, instances_card, services_card, plugins_card]
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
||||
|
||||
@app.route("/home")
|
||||
@login_required
|
||||
def home():
|
||||
|
|
@ -1035,60 +906,6 @@ def account():
|
|||
totp_qr_image=totp_qr_image,
|
||||
)
|
||||
|
||||
|
||||
def instances_builder(instances: list):
|
||||
"""
|
||||
It returns the home page in JSON format for the Vue.js builder
|
||||
"""
|
||||
builder = []
|
||||
|
||||
for instance in instances:
|
||||
# setup actions buttons
|
||||
actions = (
|
||||
["restart", "stop"]
|
||||
if instance._type == "local" and instance.health
|
||||
else (
|
||||
["reload", "stop"]
|
||||
if not instance._type == "local" and instance.health
|
||||
else ["start"] if instance._type == "local" and not instance.health else []
|
||||
)
|
||||
)
|
||||
buttons = [
|
||||
{
|
||||
"attrs": {
|
||||
"data-submit-form": f"""{{"INSTANCE_ID" : "{instance._id}", "operation" : "{action}" }}""",
|
||||
},
|
||||
"text": f"action_{action}",
|
||||
"color": "success" if action == "start" else "error" if action == "stop" else "warning",
|
||||
}
|
||||
for action in actions
|
||||
]
|
||||
|
||||
component = {
|
||||
"type": "card",
|
||||
"containerColumns": {"pc": 6, "tablet": 6, "mobile": 12},
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Instance",
|
||||
"data": {
|
||||
"pairs": [
|
||||
{"key": "instances_hostname", "value": instance.hostname},
|
||||
{"key": "instances_type", "value": instance._type},
|
||||
{"key": "instances_status", "value": "instances_active" if instance.health else "instances_inactive"},
|
||||
],
|
||||
"status": "success" if instance.health else "error",
|
||||
"title": instance.name,
|
||||
"buttons": buttons,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
builder.append(component)
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
||||
|
||||
@app.route("/instances", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def instances():
|
||||
|
|
|
|||
Loading…
Reference in a new issue