mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
add services page + update builder + add widgets
* update build.py to make services build page work * add services_builder to builder.py * move widgets to a dedicated widgets.py script * update main.py to deliver needed data * add new service button on services page * add table with use state of some main plugins * update i18n * update table component to work with modal * fix issues with table cell
This commit is contained in:
parent
7ecf6544b9
commit
ce1d8f028f
22 changed files with 1130 additions and 556 deletions
|
|
@ -2,69 +2,7 @@ import base64
|
|||
import json
|
||||
import copy
|
||||
from typing import Union
|
||||
|
||||
|
||||
def title_widget(title):
|
||||
return {
|
||||
"type": "Title",
|
||||
"data": {"title": title},
|
||||
}
|
||||
|
||||
|
||||
def table_widget(positions, header, items, filters, minWidth, title):
|
||||
return {
|
||||
"type": "Table",
|
||||
"data": {
|
||||
"title": title,
|
||||
"minWidth": minWidth,
|
||||
"header": header,
|
||||
"positions": positions,
|
||||
"items": items,
|
||||
"filters": filters,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def stat_widget(
|
||||
link: str, containerColums: dict, title: Union[str, int], subtitle: Union[str, int], subtitle_color: str, stat: Union[str, int], icon_name: str
|
||||
) -> dict:
|
||||
"""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: dict, pairs: list[dict], status: str, title: Union[str, int], buttons: list[dict]) -> dict:
|
||||
"""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,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
from widgets import title_widget, table_widget, stat_widget, instance_widget
|
||||
|
||||
|
||||
def home_builder(data: dict) -> str:
|
||||
|
|
@ -854,3 +792,379 @@ def jobs_builder(jobs):
|
|||
]
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
||||
|
||||
def services_settings(settings: dict) -> dict:
|
||||
# deep copy settings dict
|
||||
settings = settings.copy()
|
||||
# remove "SERVER_NAME" and "IS_DRAFT" key
|
||||
settings.pop("SERVER_NAME", None)
|
||||
settings.pop("IS_DRAFT", None)
|
||||
# Create table with settings remaining keys
|
||||
settings_table_items = []
|
||||
for key, value in settings.items():
|
||||
format_key = key.replace("USE_", "").replace("_", " ")
|
||||
settings_table_items.append(
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {"text": format_key},
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check" if value.get("value") == "yes" else "cross",
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
table = table_widget(
|
||||
positions=[8, 4],
|
||||
header=["services_settings_table_name", "services_settings_table_status"],
|
||||
items=settings_table_items,
|
||||
filters=[],
|
||||
minWidth="",
|
||||
title="services_settings_table_title",
|
||||
)
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def services_action(
|
||||
server_name: str = "",
|
||||
operation: str = "",
|
||||
title: str = "",
|
||||
subtitle: str = "",
|
||||
additionnal: str = "",
|
||||
is_draft: Union[bool, None] = None,
|
||||
service: dict = None,
|
||||
) -> dict:
|
||||
|
||||
buttons = [
|
||||
{
|
||||
"id": f"close-service-btn-{server_name}",
|
||||
"text": "action_close",
|
||||
"disabled": False,
|
||||
"color": "close",
|
||||
"size": "normal",
|
||||
"attrs": {"data-close-modal": ""},
|
||||
},
|
||||
]
|
||||
|
||||
if operation == "delete":
|
||||
buttons.append(
|
||||
{
|
||||
"id": f"{operation}-service-btn-{server_name}",
|
||||
"text": f"action_{operation}",
|
||||
"disabled": False,
|
||||
"color": "delete",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"data-submit-form": f"""{{"SERVER_NAME" : {server_name}, "operation" : "{operation}" }}""",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if operation == "draft":
|
||||
draft_value = "yes" if is_draft else "no"
|
||||
buttons.append(
|
||||
{
|
||||
"id": f"{operation}-service-btn-{server_name}",
|
||||
"text": "action_switch",
|
||||
"disabled": False,
|
||||
"color": "success",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"data-submit-form": f"""{{"SERVER_NAME" : {server_name}, "OLD_SERVER_NAME" : {server_name}, "operation" : "edit", "IS_DRAFT" : {draft_value} }}""",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
content = [
|
||||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": title,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if subtitle:
|
||||
content.append(
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": subtitle,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if additionnal:
|
||||
content.append(
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"bold": True,
|
||||
"text": additionnal,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if operation == "plugins":
|
||||
settings = services_settings(service)
|
||||
content.append(settings)
|
||||
|
||||
if operation == "delete":
|
||||
content.append(
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "",
|
||||
"bold": True,
|
||||
"text": server_name,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if operation == "edit" or operation == "create":
|
||||
modes = ("easy", "advanced", "raw")
|
||||
mode_buttons = []
|
||||
for mode in modes:
|
||||
mode_buttons.append(
|
||||
{
|
||||
"id": f"{operation}-service-btn-{server_name}",
|
||||
"text": f"services_mode_{mode}",
|
||||
"disabled": False,
|
||||
"color": "info",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"role": "link",
|
||||
"data-link": f"services/{mode}/{server_name}",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
content.append(
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"data": {"buttons": mode_buttons},
|
||||
}
|
||||
)
|
||||
|
||||
content.append(
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"data": {"buttons": buttons},
|
||||
},
|
||||
)
|
||||
|
||||
modal = {
|
||||
"widgets": content,
|
||||
}
|
||||
|
||||
return modal
|
||||
|
||||
|
||||
def get_services_list(services):
|
||||
data = []
|
||||
for index, service in enumerate(services):
|
||||
server_name = service["SERVER_NAME"]["value"]
|
||||
server_method = service["SERVER_NAME"]["method"]
|
||||
is_draft = True if service["IS_DRAFT"]["value"] == "yes" else False
|
||||
is_deletable = False if server_method in ("autoconf", "scheduler") else True
|
||||
|
||||
item = []
|
||||
# Get name
|
||||
item.append({"name": server_name, "type": "Text", "data": {"text": server_name}})
|
||||
item.append({"method": server_method, "type": "Text", "data": {"text": server_method}})
|
||||
item.append(
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"data": {
|
||||
"buttons": [
|
||||
{
|
||||
"id": f"open-modal-plugins-{index}",
|
||||
"text": "plugins",
|
||||
"hideText": True,
|
||||
"color": "success",
|
||||
"size": "normal",
|
||||
"iconName": "eye",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name,
|
||||
operation="plugins",
|
||||
title="services_plugins_title",
|
||||
subtitle="",
|
||||
service=service,
|
||||
),
|
||||
},
|
||||
{
|
||||
"attrs": {"data-server-name": server_name},
|
||||
"id": f"open-modal-manage-{index}",
|
||||
"text": "manage",
|
||||
"hideText": True,
|
||||
"color": "edit",
|
||||
"size": "normal",
|
||||
"iconName": "pen",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name,
|
||||
operation="edit",
|
||||
title="services_edit_title",
|
||||
subtitle="services_edit_subtitle",
|
||||
additionnal=server_name,
|
||||
),
|
||||
},
|
||||
{
|
||||
"attrs": {"data-server-name": server_name, "data-is-draft": "yes" if is_draft else "no"},
|
||||
"id": f"open-modal-draft-{index}",
|
||||
"text": "draft" if is_draft else "online",
|
||||
"hideText": True,
|
||||
"color": "blue",
|
||||
"size": "normal",
|
||||
"iconName": "document" if is_draft else "globe",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name,
|
||||
operation="draft",
|
||||
title="services_draft_title",
|
||||
subtitle="services_draft_subtitle" if is_draft else "services_online_subtitle",
|
||||
additionnal="services_draft_switch_subtitle" if is_draft else "services_online_switch_subtitle",
|
||||
is_draft=is_draft,
|
||||
),
|
||||
},
|
||||
{
|
||||
"attrs": {"data-server-name": server_name},
|
||||
"id": f"open-modal-delete-{index}",
|
||||
"text": "delete",
|
||||
"disabled": not is_deletable,
|
||||
"hideText": True,
|
||||
"color": "red",
|
||||
"size": "normal",
|
||||
"iconName": "trash",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name, operation="delete", title="services_delete_title", subtitle="services_delete_subtitle"
|
||||
),
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
data.append(item)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def services_builder(services):
|
||||
# get method for each service["SERVER_NAME"]["method"]
|
||||
methods = list(set([service["SERVER_NAME"]["method"] for service in services]))
|
||||
|
||||
services_list = get_services_list(services)
|
||||
|
||||
builder = [
|
||||
{
|
||||
"type": "card",
|
||||
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
|
||||
"widgets": [
|
||||
title_widget("services_title"),
|
||||
{
|
||||
"type": "Button",
|
||||
"data": {
|
||||
"id": "services-new",
|
||||
"text": "services_new",
|
||||
"color": "success",
|
||||
"size": "normal",
|
||||
"iconName": "plus",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(server_name="new", operation="create", title="services_create_title", subtitle="services_create_subtitle"),
|
||||
"containerClass": "col-span-12 flex justify-center",
|
||||
},
|
||||
},
|
||||
table_widget(
|
||||
positions=[4, 4, 4],
|
||||
header=[
|
||||
"services_table_name",
|
||||
"services_table_method",
|
||||
"services_table_actions",
|
||||
],
|
||||
items=services_list,
|
||||
filters=[
|
||||
{
|
||||
"filter": "table",
|
||||
"filterName": "keyword",
|
||||
"type": "keyword",
|
||||
"value": "",
|
||||
"keys": ["name"],
|
||||
"field": {
|
||||
"id": "services-keyword",
|
||||
"value": "",
|
||||
"type": "text",
|
||||
"name": "services-keyword",
|
||||
"label": "services_search",
|
||||
"placeholder": "inp_keyword",
|
||||
"isClipboard": False,
|
||||
"popovers": [
|
||||
{
|
||||
"text": "services_search_desc",
|
||||
"iconName": "info",
|
||||
},
|
||||
],
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
{
|
||||
"filter": "table",
|
||||
"filterName": "method",
|
||||
"type": "select",
|
||||
"value": "all",
|
||||
"keys": ["method"],
|
||||
"field": {
|
||||
"id": "services-methods",
|
||||
"value": "all",
|
||||
"values": methods,
|
||||
"name": "services-methods",
|
||||
"onlyDown": True,
|
||||
"label": "services_methods",
|
||||
"popovers": [
|
||||
{
|
||||
"text": "services_methods_desc",
|
||||
"iconName": "info",
|
||||
},
|
||||
],
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
{
|
||||
"filter": "table",
|
||||
"filterName": "draft",
|
||||
"type": "select",
|
||||
"value": "all",
|
||||
"keys": ["draft"],
|
||||
"field": {
|
||||
"id": "services-draft",
|
||||
"value": "all",
|
||||
"values": ["all", "online", "draft"],
|
||||
"name": "services-draft",
|
||||
"onlyDown": True,
|
||||
"label": "services_draft",
|
||||
"popovers": [
|
||||
{
|
||||
"text": "services_draft_desc",
|
||||
"iconName": "info",
|
||||
},
|
||||
],
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
],
|
||||
minWidth="md",
|
||||
title="services_table_title",
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ def move_template(folder, target_folder):
|
|||
</body>
|
||||
</html>"""
|
||||
|
||||
if "global-config" in root or "jobs" in root:
|
||||
if "global-config" in root or "jobs" in root or "services" in root:
|
||||
base_html = base_html.replace("data_server_builder[1:-1]", "data_server_builder")
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Grid from "@components/Widget/Grid.vue";
|
|||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
import Table from "@components/Widget/Table.vue";
|
||||
import Title from "@components/Widget/Title.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import { useEqualStr } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
|
|
@ -315,8 +316,12 @@ const props = defineProps({
|
|||
<Grid>
|
||||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<Table v-if="useEqualStr(widget.type, 'table')" v-bind="widget.data" />
|
||||
<Title v-if="useEqualStr(widget.type, 'title')" v-bind="widget.data" />
|
||||
<Table v-if="useEqualStr(widget.type, 'Table')" v-bind="widget.data" />
|
||||
<Button
|
||||
v-if="useEqualStr(widget.type, 'Button')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Title v-if="useEqualStr(widget.type, 'Title')" v-bind="widget.data" />
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import { useUUID } from "@utils/global.js";
|
|||
@param {Object} [attrs={}] - List of attributs to add to the button. Some attributs will conduct to additionnal script
|
||||
@param {Object|boolean} [modal=false] - We can link the button to a Modal component. We need to pass the widgets inside the modal. Button click will open the modal.
|
||||
@param {string|number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
|
||||
@param {string} [containerClass=""] - Additionnal class to the container
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
|||
|
|
@ -37,19 +37,15 @@ const props = defineProps({
|
|||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const cell = reactive({
|
||||
name: computed(() => props.type.toLowerCase()),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Text v-if="useEqualStr(cell.name, 'Text')" v-bind="props.data" />
|
||||
<Icons v-if="useEqualStr(cell.name, 'Icons')" v-bind="props.data" />
|
||||
<Fields v-if="useEqualStr(cell.name, 'Fields')" v-bind="props.data" />
|
||||
<Button v-if="useEqualStr(cell.name, 'Button')" v-bind="props.data" />
|
||||
<Text v-if="useEqualStr(props.type, 'Text')" v-bind="props.data" />
|
||||
<Icons v-if="useEqualStr(props.type, 'Icons')" v-bind="props.data" />
|
||||
<Fields v-if="useEqualStr(props.type, 'Fields')" v-bind="props.data" />
|
||||
<Button v-if="useEqualStr(props.type, 'Button')" v-bind="props.data" />
|
||||
<ButtonGroup
|
||||
v-if="useEqualStr(cell.name, 'ButtonGroup')"
|
||||
v-if="useEqualStr(props.type, 'ButtonGroup')"
|
||||
v-bind="props.data"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import Subtitle from "@components/Widget/Subtitle.vue";
|
|||
import Button from "@components/Widget/Button.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import MessageUnmatch from "@components/Message/Unmatch.vue";
|
||||
import Table from "@components/Widget/Table.vue";
|
||||
|
||||
/**
|
||||
@name Builder/Modal.vue
|
||||
|
|
@ -209,6 +210,10 @@ const emits = defineEmits(["close"]);
|
|||
v-if="useEqualStr(widget.type, 'ButtonGroup')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Table
|
||||
v-if="useEqualStr(widget.type, 'Table')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ const table = reactive({
|
|||
itemsBase: JSON.parse(JSON.stringify(props.items)),
|
||||
// items that can be filtered
|
||||
itemsFormat: JSON.parse(JSON.stringify(props.items)),
|
||||
bodyClass: "",
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -180,6 +181,12 @@ watch(
|
|||
onMounted(() => {
|
||||
getOverflow();
|
||||
setUnmatchWidth();
|
||||
|
||||
table.bodyClass = tableBody.value.closest("[data-is]:not([data-is='table'])")
|
||||
? `table-content-${tableBody.value
|
||||
.closest("[data-is]:not([data-is='table'])")
|
||||
.getAttribute("data-is")}`
|
||||
: "table-content";
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -220,7 +227,7 @@ onMounted(() => {
|
|||
:aria-hidden="!table.itemsFormat.length ? 'true' : 'false'"
|
||||
data-table-body
|
||||
ref="tableBody"
|
||||
class="table-content"
|
||||
:class="[table.bodyClass]"
|
||||
>
|
||||
<tr
|
||||
v-for="rowId in table.rowLength"
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@
|
|||
"bans_table_remain": "Remain",
|
||||
"bans_table_term": "Term",
|
||||
"bans_table_select": "Select",
|
||||
"services_new": "new service",
|
||||
"services_title": "Services",
|
||||
"services_table_name": "Name",
|
||||
"services_table_method": "Method",
|
||||
|
|
@ -275,17 +276,22 @@
|
|||
"services_draft": "draft",
|
||||
"services_online": "online",
|
||||
"services_draft_desc": "Only show services of the chosen draft status",
|
||||
"services_plugins_title": "plugins",
|
||||
"services_plugins_subtitle": "Main plugins status for this service.",
|
||||
"services_manage_title": "Manage settings",
|
||||
"services_manage_subtitle": "Choose a mode to manage service",
|
||||
"services_plugins_title": "Details",
|
||||
"services_edit_title": "Edit",
|
||||
"services_edit_subtitle": "Choose a mode to edit service",
|
||||
"services_create_title": "create service",
|
||||
"services_create_subtitle": "Choose a mode to create a new service",
|
||||
"services_mode_easy": "Easy mode",
|
||||
"services_mode_raw": "Raw mode",
|
||||
"services_mode_advanced": "Advanced mode",
|
||||
"services_draft_title": "Active status",
|
||||
"services_draft_subtitle": "Service is currently in draft (configuration is not apply).",
|
||||
"services_draft_switch_subtitle": "Switch to online ?",
|
||||
"services_online_subtitle": "Service is currently online (configuration is apply).",
|
||||
"services_online_switch_subtitle": "Switch to draft ?",
|
||||
"services_delete_title": "Delete service",
|
||||
"services_delete_subtitle": "Are you sure you want to delete the service below ?"
|
||||
"services_delete_subtitle": "Are you sure you want to delete the service below ?",
|
||||
"services_settings_table_title": "Get the activate setting state of main plugins for this service.",
|
||||
"services_settings_table_name": "Plugin",
|
||||
"services_settings_table_status": "Status"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1621,10 +1621,14 @@ body {
|
|||
@apply appearance-none block dark:text-gray-300 pb-1 text-left text-sm font-bold m-0 border-b border-gray-400;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
.table-content-card {
|
||||
@apply relative appearance-none dark:text-gray-400 block w-full rounded col-span-12 overflow-x-hidden overflow-y-auto max-h-[600px] min-h-[460px];
|
||||
}
|
||||
|
||||
.table-content-modal {
|
||||
@apply relative appearance-none dark:text-gray-400 block w-full rounded col-span-12 overflow-x-hidden overflow-y-auto max-h-[300px] min-h-[200px];
|
||||
}
|
||||
|
||||
.table-content-item {
|
||||
@apply py-2 appearance-none text-sm col-span-12 block border-b hover:bg-gray-100 dark:hover:bg-slate-700 items-center grid grid-cols-12 border-gray-300 min-h-[65px];
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -13,6 +13,91 @@
|
|||
"title": "services_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"data": {
|
||||
"id": "services-new",
|
||||
"text": "services_new",
|
||||
"color": "success",
|
||||
"size": "normal",
|
||||
"iconName": "plus",
|
||||
"iconColor": "white",
|
||||
"modal": {
|
||||
"widgets": [
|
||||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": "services_create_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "services_create_subtitle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"data": {
|
||||
"buttons": [
|
||||
{
|
||||
"id": "create-service-btn-new",
|
||||
"text": "services_mode_easy",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"role": "link",
|
||||
"data-link": "services/easy/new"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create-service-btn-new",
|
||||
"text": "services_mode_advanced",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"role": "link",
|
||||
"data-link": "services/advanced/new"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create-service-btn-new",
|
||||
"text": "services_mode_raw",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"role": "link",
|
||||
"data-link": "services/raw/new"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"data": {
|
||||
"buttons": [
|
||||
{
|
||||
"id": "close-service-btn-new",
|
||||
"text": "action_close",
|
||||
"disabled": false,
|
||||
"color": "close",
|
||||
"size": "normal",
|
||||
"attrs": {
|
||||
"data-close-modal": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"containerClass": "col-span-12 flex justify-center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Table",
|
||||
"data": {
|
||||
|
|
@ -65,9 +150,147 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"type": "Table",
|
||||
"data": {
|
||||
"text": "services_plugins_subtitle"
|
||||
"title": "services_settings_table_title",
|
||||
"minWidth": "",
|
||||
"header": [
|
||||
"services_settings_table_name",
|
||||
"services_settings_table_status"
|
||||
],
|
||||
"positions": [
|
||||
8,
|
||||
4
|
||||
],
|
||||
"items": [
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "REVERSE PROXY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "SERVE FILES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "REMOTE PHP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "AUTO LETS ENCRYPT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "CUSTOM SSL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "MODSECURITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "BAD BEHAVIOR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "LIMIT REQ"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "DNSBL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -106,13 +329,20 @@
|
|||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": "services_manage_title"
|
||||
"title": "services_edit_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "services_manage_subtitle"
|
||||
"text": "services_edit_subtitle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"bold": true,
|
||||
"text": "app1.example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -120,7 +350,7 @@
|
|||
"data": {
|
||||
"buttons": [
|
||||
{
|
||||
"id": "manage-service-btn-app1.example.com",
|
||||
"id": "edit-service-btn-app1.example.com",
|
||||
"text": "services_mode_easy",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -131,7 +361,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "manage-service-btn-app1.example.com",
|
||||
"id": "edit-service-btn-app1.example.com",
|
||||
"text": "services_mode_advanced",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -142,7 +372,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "manage-service-btn-app1.example.com",
|
||||
"id": "edit-service-btn-app1.example.com",
|
||||
"text": "services_mode_raw",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -192,7 +422,7 @@
|
|||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": "services_online"
|
||||
"title": "services_draft_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -223,7 +453,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "edit-service-btn-app1.example.com",
|
||||
"id": "draft-service-btn-app1.example.com",
|
||||
"text": "action_switch",
|
||||
"disabled": false,
|
||||
"color": "success",
|
||||
|
|
@ -341,9 +571,147 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"type": "Table",
|
||||
"data": {
|
||||
"text": "services_plugins_subtitle"
|
||||
"title": "services_settings_table_title",
|
||||
"minWidth": "",
|
||||
"header": [
|
||||
"services_settings_table_name",
|
||||
"services_settings_table_status"
|
||||
],
|
||||
"positions": [
|
||||
8,
|
||||
4
|
||||
],
|
||||
"items": [
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "REVERSE PROXY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "SERVE FILES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "REMOTE PHP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "AUTO LETS ENCRYPT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "CUSTOM SSL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "cross"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "MODSECURITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "BAD BEHAVIOR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "LIMIT REQ"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "DNSBL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -382,13 +750,20 @@
|
|||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": "services_manage_title"
|
||||
"title": "services_edit_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": "services_manage_subtitle"
|
||||
"text": "services_edit_subtitle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"bold": true,
|
||||
"text": "www.example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -396,7 +771,7 @@
|
|||
"data": {
|
||||
"buttons": [
|
||||
{
|
||||
"id": "manage-service-btn-www.example.com",
|
||||
"id": "edit-service-btn-www.example.com",
|
||||
"text": "services_mode_easy",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -407,7 +782,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "manage-service-btn-www.example.com",
|
||||
"id": "edit-service-btn-www.example.com",
|
||||
"text": "services_mode_advanced",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -418,7 +793,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "manage-service-btn-www.example.com",
|
||||
"id": "edit-service-btn-www.example.com",
|
||||
"text": "services_mode_raw",
|
||||
"disabled": false,
|
||||
"color": "info",
|
||||
|
|
@ -468,7 +843,7 @@
|
|||
{
|
||||
"type": "Title",
|
||||
"data": {
|
||||
"title": "services_draft"
|
||||
"title": "services_draft_title"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -499,7 +874,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "edit-service-btn-www.example.com",
|
||||
"id": "draft-service-btn-www.example.com",
|
||||
"text": "action_switch",
|
||||
"disabled": false,
|
||||
"color": "success",
|
||||
|
|
|
|||
|
|
@ -55,8 +55,51 @@ def table_widget(positions, header, items, filters, minWidth, title):
|
|||
}
|
||||
|
||||
|
||||
def services_settings(settings: dict) -> dict:
|
||||
# deep copy settings dict
|
||||
settings = settings.copy()
|
||||
# remove "SERVER_NAME" and "IS_DRAFT" key
|
||||
settings.pop("SERVER_NAME", None)
|
||||
settings.pop("IS_DRAFT", None)
|
||||
# Create table with settings remaining keys
|
||||
settings_table_items = []
|
||||
for key, value in settings.items():
|
||||
format_key = key.replace("USE_", "").replace("_", " ")
|
||||
settings_table_items.append(
|
||||
[
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {"text": format_key},
|
||||
},
|
||||
{
|
||||
"type": "Icons",
|
||||
"data": {
|
||||
"iconName": "check" if value.get("value") == "yes" else "cross",
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
table = table_widget(
|
||||
positions=[8, 4],
|
||||
header=["services_settings_table_name", "services_settings_table_status"],
|
||||
items=settings_table_items,
|
||||
filters=[],
|
||||
minWidth="",
|
||||
title="services_settings_table_title",
|
||||
)
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def services_action(
|
||||
server_name: str = "", operation: str = "", title: str = "", subtitle: str = "", additionnal: str = "", is_draft: Union[bool, None] = None
|
||||
server_name: str = "",
|
||||
operation: str = "",
|
||||
title: str = "",
|
||||
subtitle: str = "",
|
||||
additionnal: str = "",
|
||||
is_draft: Union[bool, None] = None,
|
||||
service: dict = None,
|
||||
) -> dict:
|
||||
|
||||
buttons = [
|
||||
|
|
@ -84,7 +127,7 @@ def services_action(
|
|||
},
|
||||
)
|
||||
|
||||
if operation == "edit":
|
||||
if operation == "draft":
|
||||
draft_value = "yes" if is_draft else "no"
|
||||
buttons.append(
|
||||
{
|
||||
|
|
@ -106,14 +149,18 @@ def services_action(
|
|||
"title": title,
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": subtitle,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if subtitle:
|
||||
content.append(
|
||||
{
|
||||
"type": "Text",
|
||||
"data": {
|
||||
"text": subtitle,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if additionnal:
|
||||
content.append(
|
||||
{
|
||||
|
|
@ -125,6 +172,10 @@ def services_action(
|
|||
}
|
||||
)
|
||||
|
||||
if operation == "plugins":
|
||||
settings = services_settings(service)
|
||||
content.append(settings)
|
||||
|
||||
if operation == "delete":
|
||||
content.append(
|
||||
{
|
||||
|
|
@ -137,7 +188,7 @@ def services_action(
|
|||
}
|
||||
)
|
||||
|
||||
if operation == "manage":
|
||||
if operation == "edit" or operation == "create":
|
||||
modes = ("easy", "advanced", "raw")
|
||||
mode_buttons = []
|
||||
for mode in modes:
|
||||
|
|
@ -202,7 +253,11 @@ def get_services_list(services):
|
|||
"iconName": "eye",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name, operation="plugins", title="services_plugins_title", subtitle="services_plugins_subtitle"
|
||||
server_name=server_name,
|
||||
operation="plugins",
|
||||
title="services_plugins_title",
|
||||
subtitle="",
|
||||
service=service,
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -215,7 +270,11 @@ def get_services_list(services):
|
|||
"iconName": "pen",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name, operation="manage", title="services_manage_title", subtitle="services_manage_subtitle"
|
||||
server_name=server_name,
|
||||
operation="edit",
|
||||
title="services_edit_title",
|
||||
subtitle="services_edit_subtitle",
|
||||
additionnal=server_name,
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -229,8 +288,8 @@ def get_services_list(services):
|
|||
"iconColor": "white",
|
||||
"modal": services_action(
|
||||
server_name=server_name,
|
||||
operation="edit",
|
||||
title="services_draft" if is_draft else "services_online",
|
||||
operation="draft",
|
||||
title="services_draft_title",
|
||||
subtitle="services_draft_subtitle" if is_draft else "services_online_subtitle",
|
||||
additionnal="services_draft_switch_subtitle" if is_draft else "services_online_switch_subtitle",
|
||||
is_draft=is_draft,
|
||||
|
|
@ -272,6 +331,19 @@ def services_builder(services):
|
|||
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
|
||||
"widgets": [
|
||||
title_widget("services_title"),
|
||||
{
|
||||
"type": "Button",
|
||||
"data": {
|
||||
"id": "services-new",
|
||||
"text": "services_new",
|
||||
"color": "success",
|
||||
"size": "normal",
|
||||
"iconName": "plus",
|
||||
"iconColor": "white",
|
||||
"modal": services_action(server_name="new", operation="create", title="services_create_title", subtitle="services_create_subtitle"),
|
||||
"containerClass": "col-span-12 flex justify-center",
|
||||
},
|
||||
},
|
||||
table_widget(
|
||||
positions=[4, 4, 4],
|
||||
header=[
|
||||
|
|
@ -353,7 +425,7 @@ def services_builder(services):
|
|||
title="services_table_title",
|
||||
),
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
return builder
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -50,6 +50,7 @@ export default defineConfig({
|
|||
"./dashboard/pages/global-config/index.html"
|
||||
),
|
||||
jobs: resolve(__dirname, "./dashboard/pages/jobs/index.html"),
|
||||
services: resolve(__dirname, "./dashboard/pages/services/index.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
236
src/ui/main.py
236
src/ui/main.py
|
|
@ -10,7 +10,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, global_config_builder, jobs_builder
|
||||
from builder import home_builder, instances_builder, global_config_builder, jobs_builder, services_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:
|
||||
|
|
@ -1052,124 +1052,6 @@ def get_service_data():
|
|||
return config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged
|
||||
|
||||
|
||||
# @app.route("/services", methods=["GET", "POST"])
|
||||
# @login_required
|
||||
# def services():
|
||||
# if request.method == "POST":
|
||||
# if DB.readonly:
|
||||
# return handle_error("Database is in read-only mode", "services")
|
||||
|
||||
# verify_data_in_form(
|
||||
# data={"operation": ("edit", "new", "delete")},
|
||||
# err_message="Invalid operation parameter on /services.",
|
||||
# redirect_url="services",
|
||||
# )
|
||||
|
||||
# config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged = get_service_data()
|
||||
|
||||
# if request.form["operation"] == "edit":
|
||||
# if is_draft_unchanged and len(variables) == 1 and "SERVER_NAME" in variables and server_name == old_server_name:
|
||||
# return handle_error("The service was not edited because no values were changed.", "services", True)
|
||||
|
||||
# if request.form["operation"] == "new" and not variables:
|
||||
# return handle_error("The service was not created because all values had the default value.", "services", True)
|
||||
|
||||
# # Delete
|
||||
# if request.form["operation"] == "delete":
|
||||
|
||||
# is_service = app.config["CONFIG"].check_variables({"SERVER_NAME": request.form["SERVER_NAME"]}, config)
|
||||
|
||||
# if not is_service:
|
||||
# error_message(f"Error while deleting the service {request.form['SERVER_NAME']}")
|
||||
|
||||
# if config.get(f"{request.form['SERVER_NAME'].split(' ')[0]}_SERVER_NAME", {"method": "scheduler"})["method"] != "ui":
|
||||
# return handle_error("The service cannot be deleted because it has not been created with the UI.", "services", True)
|
||||
|
||||
# db_metadata = DB.get_metadata()
|
||||
|
||||
# def update_services(threaded: bool = False):
|
||||
# wait_applying()
|
||||
|
||||
# manage_bunkerweb(
|
||||
# "services",
|
||||
# variables,
|
||||
# old_server_name,
|
||||
# variables.get("SERVER_NAME", ""),
|
||||
# operation=operation,
|
||||
# is_draft=is_draft,
|
||||
# was_draft=was_draft,
|
||||
# threaded=threaded,
|
||||
# )
|
||||
|
||||
# ui_data = get_ui_data()
|
||||
|
||||
# if any(
|
||||
# v
|
||||
# for k, v in db_metadata.items()
|
||||
# if k in ("custom_configs_changed", "external_plugins_changed", "pro_plugins_changed", "plugins_config_changed", "instances_changed")
|
||||
# ):
|
||||
# ui_data["RELOADING"] = True
|
||||
# ui_data["LAST_RELOAD"] = time()
|
||||
# Thread(target=update_services, args=(True,)).start()
|
||||
# else:
|
||||
# update_services()
|
||||
|
||||
# ui_data["CONFIG_CHANGED"] = True
|
||||
|
||||
# with LOCK:
|
||||
# TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
|
||||
|
||||
# message = ""
|
||||
|
||||
# if request.form["operation"] == "new":
|
||||
# message = f"Creating {'draft ' if is_draft else ''}service {variables.get('SERVER_NAME', '').split(' ')[0]}"
|
||||
# elif request.form["operation"] == "edit":
|
||||
# message = f"Saving configuration for {'draft ' if is_draft else ''}service {old_server_name.split(' ')[0]}"
|
||||
# elif request.form["operation"] == "delete":
|
||||
# message = f"Deleting {'draft ' if was_draft and is_draft else ''}service {request.form.get('SERVER_NAME', '').split(' ')[0]}"
|
||||
|
||||
# return redirect(url_for("loading", next=url_for("services"), message=message))
|
||||
|
||||
# # Display services
|
||||
# services = []
|
||||
# tmp_config = DB.get_config(methods=True, with_drafts=True).copy()
|
||||
# service_names = tmp_config["SERVER_NAME"]["value"].split(" ")
|
||||
|
||||
# table_settings = (
|
||||
# "USE_REVERSE_PROXY",
|
||||
# "IS_DRAFT",
|
||||
# "SERVE_FILES",
|
||||
# "REMOTE_PHP",
|
||||
# "AUTO_LETS_ENCRYPT",
|
||||
# "USE_CUSTOM_SSL",
|
||||
# "USE_MODSECURITY",
|
||||
# "USE_BAD_BEHAVIOR",
|
||||
# "USE_LIMIT_REQ",
|
||||
# "USE_DNSBL",
|
||||
# "SERVER_NAME",
|
||||
# )
|
||||
|
||||
# for service in service_names:
|
||||
# service_settings = {}
|
||||
|
||||
# # For each needed setting, get the service value if one, else the global (value), else default value
|
||||
# for setting in table_settings:
|
||||
# value = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"value": None}))["value"]
|
||||
# method = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"method": None}))["method"]
|
||||
# is_global = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"global": None}))["global"]
|
||||
# service_settings[setting] = {"value": value, "method": method, "global": is_global}
|
||||
|
||||
# services.append(service_settings)
|
||||
|
||||
# services.sort(key=lambda x: x["SERVER_NAME"]["value"])
|
||||
|
||||
# return render_template(
|
||||
# "services.html",
|
||||
# services=services,
|
||||
# global_config=global_config,
|
||||
# )
|
||||
|
||||
|
||||
@app.route("/services", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def services():
|
||||
|
|
@ -1248,6 +1130,122 @@ def services():
|
|||
|
||||
return redirect(url_for("loading", next=url_for("services"), message=message))
|
||||
|
||||
# Display services
|
||||
services = []
|
||||
tmp_config = DB.get_config(methods=True, with_drafts=True).copy()
|
||||
service_names = tmp_config["SERVER_NAME"]["value"].split(" ")
|
||||
|
||||
table_settings = (
|
||||
"USE_REVERSE_PROXY",
|
||||
"IS_DRAFT",
|
||||
"SERVE_FILES",
|
||||
"REMOTE_PHP",
|
||||
"AUTO_LETS_ENCRYPT",
|
||||
"USE_CUSTOM_SSL",
|
||||
"USE_MODSECURITY",
|
||||
"USE_BAD_BEHAVIOR",
|
||||
"USE_LIMIT_REQ",
|
||||
"USE_DNSBL",
|
||||
"SERVER_NAME",
|
||||
)
|
||||
|
||||
for service in service_names:
|
||||
service_settings = {}
|
||||
|
||||
# For each needed setting, get the service value if one, else the global (value), else default value
|
||||
for setting in table_settings:
|
||||
value = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"value": None}))["value"]
|
||||
method = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"method": None}))["method"]
|
||||
is_global = tmp_config.get(f"{service}_{setting}", tmp_config.get(setting, {"global": None}))["global"]
|
||||
service_settings[setting] = {"value": value, "method": method, "global": is_global}
|
||||
|
||||
services.append(service_settings)
|
||||
|
||||
services.sort(key=lambda x: x["SERVER_NAME"]["value"])
|
||||
|
||||
data_server_builder = services_builder(services)
|
||||
|
||||
return render_template("services.html", data_server_builder=data_server_builder)
|
||||
|
||||
|
||||
@app.route("/services/raw/{service_name}", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def services_raw(service_name: str):
|
||||
if request.method == "POST":
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "services")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"operation": ("edit", "new", "delete")},
|
||||
err_message="Invalid operation parameter on /services.",
|
||||
redirect_url="services",
|
||||
)
|
||||
|
||||
config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged = get_service_data()
|
||||
|
||||
if request.form["operation"] == "edit":
|
||||
if is_draft_unchanged and len(variables) == 1 and "SERVER_NAME" in variables and server_name == old_server_name:
|
||||
return handle_error("The service was not edited because no values were changed.", "services", True)
|
||||
|
||||
if request.form["operation"] == "new" and not variables:
|
||||
return handle_error("The service was not created because all values had the default value.", "services", True)
|
||||
|
||||
# Delete
|
||||
if request.form["operation"] == "delete":
|
||||
|
||||
is_service = app.config["CONFIG"].check_variables({"SERVER_NAME": request.form["SERVER_NAME"]}, config)
|
||||
|
||||
if not is_service:
|
||||
error_message(f"Error while deleting the service {request.form['SERVER_NAME']}")
|
||||
|
||||
if config.get(f"{request.form['SERVER_NAME'].split(' ')[0]}_SERVER_NAME", {"method": "scheduler"})["method"] != "ui":
|
||||
return handle_error("The service cannot be deleted because it has not been created with the UI.", "services", True)
|
||||
|
||||
db_metadata = DB.get_metadata()
|
||||
|
||||
def update_services(threaded: bool = False):
|
||||
wait_applying()
|
||||
|
||||
manage_bunkerweb(
|
||||
"services",
|
||||
variables,
|
||||
old_server_name,
|
||||
variables.get("SERVER_NAME", ""),
|
||||
operation=operation,
|
||||
is_draft=is_draft,
|
||||
was_draft=was_draft,
|
||||
threaded=threaded,
|
||||
)
|
||||
|
||||
ui_data = get_ui_data()
|
||||
|
||||
if any(
|
||||
v
|
||||
for k, v in db_metadata.items()
|
||||
if k in ("custom_configs_changed", "external_plugins_changed", "pro_plugins_changed", "plugins_config_changed", "instances_changed")
|
||||
):
|
||||
ui_data["RELOADING"] = True
|
||||
ui_data["LAST_RELOAD"] = time()
|
||||
Thread(target=update_services, args=(True,)).start()
|
||||
else:
|
||||
update_services()
|
||||
|
||||
ui_data["CONFIG_CHANGED"] = True
|
||||
|
||||
with LOCK:
|
||||
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
|
||||
|
||||
message = ""
|
||||
|
||||
if request.form["operation"] == "new":
|
||||
message = f"Creating {'draft ' if is_draft else ''}service {variables.get('SERVER_NAME', '').split(' ')[0]}"
|
||||
elif request.form["operation"] == "edit":
|
||||
message = f"Saving configuration for {'draft ' if is_draft else ''}service {old_server_name.split(' ')[0]}"
|
||||
elif request.form["operation"] == "delete":
|
||||
message = f"Deleting {'draft ' if was_draft and is_draft else ''}service {request.form.get('SERVER_NAME', '').split(' ')[0]}"
|
||||
|
||||
return redirect(url_for("loading", next=url_for("services"), message=message))
|
||||
|
||||
# Display services
|
||||
services = []
|
||||
global_config = DB.get_config(methods=True, with_drafts=True)
|
||||
|
|
|
|||
11
src/ui/templates/global-config.html
vendored
11
src/ui/templates/global-config.html
vendored
|
|
@ -7,13 +7,10 @@
|
|||
<link rel="stylesheet" href="css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | Global config</title>
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/global_config-BiGxQ47g.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B9u3FFBX.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Subtitle-BwEtagqe.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-C-EyDvow.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Filter-DYZ0nQ3V.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/form-V4O4B50c.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/Filter-D2kv0NCW.css">
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/global_config-_p4bx2iA.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
6
src/ui/templates/home.html
vendored
6
src/ui/templates/home.html
vendored
|
|
@ -7,10 +7,8 @@
|
|||
<link rel="stylesheet" href="css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | Home</title>
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/home-BzEODgxW.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B9u3FFBX.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Subtitle-BwEtagqe.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-C-EyDvow.js">
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/home-C5vSVPv_.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
7
src/ui/templates/instances.html
vendored
7
src/ui/templates/instances.html
vendored
|
|
@ -7,9 +7,10 @@
|
|||
<link rel="stylesheet" href="css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | Instances</title>
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/instances-BtiykE6Z.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B9u3FFBX.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/form-V4O4B50c.js">
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/instances-DlYdXYfk.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
9
src/ui/templates/jobs.html
vendored
9
src/ui/templates/jobs.html
vendored
|
|
@ -7,11 +7,10 @@
|
|||
<link rel="stylesheet" href="css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | Jobs</title>
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/jobs-kphU0FKk.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B9u3FFBX.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-C-EyDvow.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Filter-DYZ0nQ3V.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/Filter-D2kv0NCW.css">
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/jobs-DsETM5wn.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
330
src/ui/templates/services.html
vendored
330
src/ui/templates/services.html
vendored
|
|
@ -1,301 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% set attribute_name = "services" %}
|
||||
{% set methods = ["all"] %}
|
||||
{% set states = ["all", "draft", "online"] %}
|
||||
{% set draft_services = [] %}
|
||||
{% set online_services = [] %}
|
||||
{% for service in services %}
|
||||
{% if service['SERVER_NAME']['method'] not in methods %}
|
||||
{% if methods.append(service['SERVER_NAME']['method']) %}{% endif %}
|
||||
{% endif %}
|
||||
{% if service.get('IS_DRAFT', 'no') == "yes" %}
|
||||
{% if draft_services.append(1) %}{% endif %}
|
||||
{% else %}
|
||||
{% if online_services.append(1) %}{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{# Get name of multiples #}
|
||||
{% set multiple_settings = [] %}
|
||||
{% for plugin in plugins %}
|
||||
{% for setting, value in plugin.get('settings', {}).items() %}
|
||||
{% if value.get("multiple", "") %}
|
||||
{% if multiple_settings.append(setting) %}{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<input class="hidden" data-plugins-multiple='{{ multiple_settings }}' />
|
||||
<!-- actions -->
|
||||
<div data-{{ attribute_name }}-service data-settings="{}" class="col-span-12 relative flex justify-center min-w-0 break-words rounded-2xl bg-clip-border">
|
||||
<div data-is-draft class="hidden" data-value="no"></div>
|
||||
<div data-service-method class="hidden" data-value="ui"></div>
|
||||
<button {% if is_readonly %}disabled{% endif %} data-{{ attribute_name }}-action="new" data-{{ attribute_name }}-name="service" data-old-name data-value="new" data-settings="{}" type="button" class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:bg-green-500/90 duration-300 dark:text-gray-100 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
|
||||
<span class="mr-2">new service</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-7 h-7">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- end actions -->
|
||||
{% if services|length >= 4 %}
|
||||
<!-- service info and actions -->
|
||||
<div class="p-0 sm:mx-2 md:mx-4 grid grid-cols-12 col-span-12 md:gap-x-4 gap-y-4 relative min-w-0 break-words rounded-2xl bg-clip-border">
|
||||
<!-- info-->
|
||||
{% set infos = [
|
||||
{"name" : "SERVICES TOTAL", "data" : services|length|string},
|
||||
{"name" : "TOTAL DRAFT", "data" : draft_services|length|string},
|
||||
{"name" : "TOTAL ONLINE", "data" : online_services|length|string},
|
||||
] %}
|
||||
{% include "card_info.html" %}
|
||||
<!-- filter -->
|
||||
{% set filters = [
|
||||
{
|
||||
"type": "input",
|
||||
"name": "Search",
|
||||
"label": "search",
|
||||
"id": "service-name-keyword",
|
||||
"placeholder": "service name",
|
||||
"pattern": "(.*?)"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "Method",
|
||||
"id": "method",
|
||||
"value": "all",
|
||||
"values": methods
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "State",
|
||||
"id": "state",
|
||||
"value": "all",
|
||||
"values": states
|
||||
}
|
||||
] %}
|
||||
{% include "card_filter.html" %}
|
||||
{% include "filter_nomatch.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end service info and actions -->
|
||||
<!-- services container-->
|
||||
<div class="p-0 sm:mx-2 md:mx-4 md:px-1 grid grid-cols-12 col-span-12 md:gap-x-4 gap-y-4 relative min-w-0 break-words rounded-2xl bg-clip-border">
|
||||
{% if services|length == 0 %}
|
||||
<div class="col-span-12 sm:col-span-4 sm:col-start-5">
|
||||
<div class="transition duration-300 ease-in-out dark:opacity-90 text-center relative w-full p-4 text-white bg-blue-500 rounded-lg">
|
||||
No service to show
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if services|length > 0 %}
|
||||
<!-- end filter -->
|
||||
{% for service in services %}
|
||||
{% set id_server_name = service["SERVER_NAME"]['value'].replace(".", "-") %}
|
||||
<div data-{{ attribute_name }}-card data-{{ attribute_name }}-name="{{ service["SERVER_NAME"]['value'] }}" data-{{ attribute_name }}-method="{{ service["SERVER_NAME"]['method'] }}" data-{{ attribute_name }}-state="{{ "draft" if service.get('IS_DRAFT', 'no') == "yes" else "online" }}" data-settings="{{ service['settings'] }}" data-{{ attribute_name }}-service="{{ service['SERVER_NAME']['value'] }}" class="flex flex-col justify-between dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
|
||||
<div data-old-name
|
||||
class="hidden"
|
||||
data-value="{{ service['SERVER_NAME']['full_value'] }}"></div>
|
||||
<div data-is-draft
|
||||
class="hidden"
|
||||
data-value="{% if service.get('IS_DRAFT', 'no') == 'yes' %}yes{% else %}no{% endif %}"></div>
|
||||
<div data-service-method
|
||||
class="hidden"
|
||||
data-value="{{ service['SERVER_NAME']['method'] }}"></div>
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex flex-col">
|
||||
<h5 class="break-all transition duration-300 ease-in-out text-center sm:text-left mb-1 mr-2 font-bold dark:text-white/90">
|
||||
{{ service["SERVER_NAME"]['value'] }}
|
||||
</h5>
|
||||
<h6 class="text-left sm:mb-2 font-semibold text-gray-600 dark:text-white/80">
|
||||
{{ service["SERVER_NAME"]['method'] }}
|
||||
</h6>
|
||||
</div>
|
||||
{% if service.get('IS_DRAFT', "no") == "yes" and service["SERVER_NAME"]['method'] in ["ui", "default"] %}
|
||||
<button class="group relative">
|
||||
<p data-{{ attribute_name }}-state="draft" class="dark:text-gray-300 -z-10 opacity-0 group-hover:z-10 group-hover:opacity-100 transition fixed bg-white dark:bg-slate-800 rounded right-12 px-1 py-0.5">
|
||||
Draft
|
||||
</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 fill-gray-700 dark:fill-gray-300 cursor-pointer-none">
|
||||
<path fill-rule="evenodd" d="M10.5 3.798v5.02a3 3 0 0 1-.879 2.121l-2.377 2.377a9.845 9.845 0 0 1 5.091 1.013 8.315 8.315 0 0 0 5.713.636l.285-.071-3.954-3.955a3 3 0 0 1-.879-2.121v-5.02a23.614 23.614 0 0 0-3 0Zm4.5.138a.75.75 0 0 0 .093-1.495A24.837 24.837 0 0 0 12 2.25a25.048 25.048 0 0 0-3.093.191A.75.75 0 0 0 9 3.936v4.882a1.5 1.5 0 0 1-.44 1.06l-6.293 6.294c-1.62 1.621-.903 4.475 1.471 4.88 2.686.46 5.447.698 8.262.698 2.816 0 5.576-.239 8.262-.697 2.373-.406 3.092-3.26 1.47-4.881L15.44 9.879A1.5 1.5 0 0 1 15 8.818V3.936Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="group relative">
|
||||
<p data-{{ attribute_name }}-state="online" class="dark:text-gray-300 -z-10 opacity-0 group-hover:z-10 group-hover:opacity-100 transition fixed bg-white dark:bg-slate-800 rounded right-12 px-1 py-0.5">
|
||||
Online
|
||||
</p>
|
||||
<svg data-toggle-draft="false"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 fill-gray-700 dark:fill-gray-300 cursor-pointer-none">
|
||||
<path d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z" />
|
||||
</svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set details = [
|
||||
{
|
||||
"name": "Reverse proxy",
|
||||
"settings": [
|
||||
"USE_REVERSE_PROXY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Serve files",
|
||||
"settings": [
|
||||
"SERVE_FILES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Remote PHP",
|
||||
"settings": [
|
||||
"REMOTE_PHP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HTTPS",
|
||||
"settings": [
|
||||
"AUTO_LETS_ENCRYPT",
|
||||
"USE_CUSTOM_SSL",
|
||||
"GENERATE_SELF_SIGNED_SSL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ModSecurity",
|
||||
"settings": [
|
||||
"USE_MODSECURITY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Bad behavior",
|
||||
"settings": [
|
||||
"USE_BAD_BEHAVIOR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Limit req",
|
||||
"settings": [
|
||||
"USE_LIMIT_REQ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DNSBL",
|
||||
"settings": [
|
||||
"USE_DNSBL"
|
||||
]
|
||||
}
|
||||
] %}
|
||||
<!-- detail list -->
|
||||
<div role="grid"
|
||||
class="w-full grid grid-cols-12 justify-items-center sm:justify-items-start gap-2 mt-4 mb-6 ml-3 sm:ml-1">
|
||||
{% for detail in details %}
|
||||
{% set use = [] %}
|
||||
{% for setting in detail['settings'] %}
|
||||
{% if service[setting]['value'] == 'yes' %}
|
||||
{% if use.append(1) %}{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<!-- detail -->
|
||||
<div role="row" class="flex items-center col-span-12 sm:col-span-6">
|
||||
<p role="gridcell"
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 ">
|
||||
{{ detail['name'] }}
|
||||
</p>
|
||||
<p role="gridcell"
|
||||
class="transition duration-300 ease-in-out dark:opacity-90 pl-2 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-gray-500 ">
|
||||
{% if use %}
|
||||
<span class="sr-only">yes</span>
|
||||
<svg class="h-4 w-4 fill-green-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512">
|
||||
<path d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z" />
|
||||
</svg>
|
||||
{% else %}
|
||||
<span class="sr-only">no</span>
|
||||
<svg class="h-4 w-4 fill-red-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 320 512">
|
||||
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<!-- end detail -->
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- end detail list-->
|
||||
<!-- button list-->
|
||||
<div class="relative w-full flex justify-center sm:justify-end">
|
||||
<a aria-label="access service url"
|
||||
href="http://{{ service['SERVER_NAME']['value'] }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="dark:brightness-90 z-20 mx-1 bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md">
|
||||
<svg class="h-6 w-6 fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512">
|
||||
<path d="M288 32c-17.7 0-32 14.3-32 32s14.3 32 32 32h50.7L169.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L384 141.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V64c0-17.7-14.3-32-32-32H288zM80 64C35.8 64 0 99.8 0 144V400c0 44.2 35.8 80 80 80H336c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v80c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h80c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% set action_buttons = [
|
||||
{
|
||||
"name": "clone",
|
||||
"label": "clone service settings",
|
||||
"color": "emerald-500"
|
||||
},
|
||||
{
|
||||
"name": "edit",
|
||||
"label": "edit service settings",
|
||||
"color": "yellow-500"
|
||||
}
|
||||
] %}
|
||||
{% if service["SERVER_NAME"]['method'] == "ui" %}
|
||||
{% if action_buttons.append({"name" : "delete", "label" : "delete service settings", "color" : "red-500"}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for button in action_buttons %}
|
||||
<button {% if button['name'] == "clone" and is_readonly or button['name'] == "delete" and is_readonly %}disabled{% endif %} {% if button['name'] == "clone" or button['name'] == "edit" %}data-settings="{{ service['settings'] }}"{% endif %} {% if button['name'] == "new" %}data-settings="{}"{% endif %} data-{{ attribute_name }}-action="{{ button['name'] }}" aria-label="{{ button['label'] }}" data-{{ attribute_name }}-name="{{ service['SERVER_NAME']['value'] }}" class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:brightness-90 z-20 mx-1 bg-{{ button['color'] }} hover:bg-{{ button['color'] }}/80 focus:bg-{{ button['color'] }}/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md">
|
||||
{% if button['name'] == "clone" %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6 fill-white">
|
||||
<path fill-rule="evenodd" d="M17.663 3.118c.225.015.45.032.673.05C19.876 3.298 21 4.604 21 6.109v9.642a3 3 0 0 1-3 3V16.5c0-5.922-4.576-10.775-10.384-11.217.324-1.132 1.3-2.01 2.548-2.114.224-.019.448-.036.673-.051A3 3 0 0 1 13.5 1.5H15a3 3 0 0 1 2.663 1.618ZM12 4.5A1.5 1.5 0 0 1 13.5 3H15a1.5 1.5 0 0 1 1.5 1.5H12Z" clip-rule="evenodd" />
|
||||
<path d="M3 8.625c0-1.036.84-1.875 1.875-1.875h.375A3.75 3.75 0 0 1 9 10.5v1.875c0 1.036.84 1.875 1.875 1.875h1.875A3.75 3.75 0 0 1 16.5 18v2.625c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625v-12Z" />
|
||||
<path d="M10.5 10.5a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963 5.23 5.23 0 0 0-3.434-1.279h-1.875a.375.375 0 0 1-.375-.375V10.5Z" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
{% if button['name'] == "edit" %}
|
||||
<svg class="h-6 w-6 fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512">
|
||||
<path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336c44.2 0 80-35.8 80-80s-35.8-80-80-80s-80 35.8-80 80s35.8 80 80 80z" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
{% if button['name'] == "delete" %}
|
||||
<svg class="h-6 w-6 fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512">
|
||||
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- end button list-->
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end services container-->
|
||||
<!-- modal -->
|
||||
{% include "services_modal.html" %}
|
||||
{% endblock content %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<link rel="stylesheet" href="css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | Services</title>
|
||||
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/services-CSE-2AMK.js"></script>
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
|
||||
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
|
||||
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% set data_server_flash = [] %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
|
||||
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
|
||||
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
|
||||
<div class='hidden' data-server-builder='{{data_server_builder}}'></div>
|
||||
<div id='app'></div>
|
||||
</body>
|
||||
</html>
|
||||
67
src/ui/widgets.py
Normal file
67
src/ui/widgets.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import base64
|
||||
import json
|
||||
import copy
|
||||
from typing import Union
|
||||
|
||||
|
||||
def title_widget(title: str) -> dict:
|
||||
return {
|
||||
"type": "Title",
|
||||
"data": {"title": title},
|
||||
}
|
||||
|
||||
|
||||
def table_widget(positions: list[int], header: list[str], items: list[dict], filters: list[dict], minWidth: str, title: str) -> dict:
|
||||
return {
|
||||
"type": "Table",
|
||||
"data": {
|
||||
"title": title,
|
||||
"minWidth": minWidth,
|
||||
"header": header,
|
||||
"positions": positions,
|
||||
"items": items,
|
||||
"filters": filters,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def stat_widget(
|
||||
link: str, containerColums: dict, title: Union[str, int], subtitle: Union[str, int], subtitle_color: str, stat: Union[str, int], icon_name: str
|
||||
) -> dict:
|
||||
"""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: dict, pairs: list[dict], status: str, title: Union[str, int], buttons: list[dict]) -> dict:
|
||||
"""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,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
Loading…
Reference in a new issue