mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Add reload to instances + start configs
This commit is contained in:
parent
e218f8e621
commit
fea36fb073
20 changed files with 1315 additions and 1207 deletions
402
src/ui/client/builder/pages/configs.py
Normal file
402
src/ui/client/builder/pages/configs.py
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
from .utils.widgets import (
|
||||
button_widget,
|
||||
button_group_widget,
|
||||
title_widget,
|
||||
subtitle_widget,
|
||||
text_widget,
|
||||
tabulator_widget,
|
||||
input_widget,
|
||||
icons_widget,
|
||||
regular_widget,
|
||||
unmatch_widget,
|
||||
)
|
||||
from .utils.table import add_column
|
||||
from .utils.format import get_fields_from_field
|
||||
from typing import Optional
|
||||
|
||||
columns = [
|
||||
add_column(title="Name", field="name", formatter="text"),
|
||||
add_column(title="Type", field="type", formatter="text"),
|
||||
add_column(title="global", field="global", formatter="icons"),
|
||||
add_column(
|
||||
title="services", field="services", formatter="buttonGroup"
|
||||
), # We will display a button with a modal that show all services apply. Case global, show all services.
|
||||
add_column(
|
||||
title="actions", field="actions", formatter="buttonGroup"
|
||||
), # edit button that will switch to the form using display store + delete with modal to confirm
|
||||
]
|
||||
|
||||
|
||||
def configs_filter(types: Optional[list] = None) -> list: # healths = "up", "down", "loading"
|
||||
filters = [
|
||||
{
|
||||
"type": "like",
|
||||
"fields": ["name"],
|
||||
"setting": {
|
||||
"id": "input-search-name",
|
||||
"name": "input-search-name",
|
||||
"label": "configs_search_name", # keep it (a18n)
|
||||
"value": "",
|
||||
"inpType": "input",
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
"fieldSize": "sm",
|
||||
"popovers": [
|
||||
{
|
||||
"iconName": "info",
|
||||
"text": "configs_search_name_desc",
|
||||
}
|
||||
],
|
||||
"placeholder": "configs_search_name_placeholder", # keep it (a18n)
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "=",
|
||||
"fields": ["global"],
|
||||
"setting": {
|
||||
"id": "select-global",
|
||||
"name": "select-global",
|
||||
"label": "configs_select_global", # keep it (a18n)
|
||||
"value": "all", # keep "all"
|
||||
"values": ["all", "yes", "no"], # keep
|
||||
"inpType": "select",
|
||||
"onlyDown": True,
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
"fieldSize": "sm",
|
||||
"popovers": [
|
||||
{
|
||||
"iconName": "info",
|
||||
"text": "configs_select_global_desc",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if types is not None and len(types) >= 2:
|
||||
filters.append(
|
||||
{
|
||||
"type": "=",
|
||||
"fields": ["type"],
|
||||
"setting": {
|
||||
"id": "select-type",
|
||||
"name": "select-type",
|
||||
"label": "configs_select_type", # keep it (a18n)
|
||||
"value": "all", # keep "all"
|
||||
"values": ["all"] + types,
|
||||
"inpType": "select",
|
||||
"onlyDown": True,
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
"fieldSize": "sm",
|
||||
"popovers": [
|
||||
{
|
||||
"iconName": "info",
|
||||
"text": "configs_select_type_desc",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def configs_item(
|
||||
is_global: bool, # "yes" or "no"
|
||||
filename: str = "",
|
||||
config_type: str = "",
|
||||
types: Optional[list] = None,
|
||||
services: Optional[list] = None,
|
||||
config_value: str = "",
|
||||
is_new: bool = False,
|
||||
display_index: int = 1,
|
||||
):
|
||||
global_text = "yes" if is_global else "no"
|
||||
|
||||
actions = [
|
||||
button_widget(
|
||||
id=f"edit-user-{filename}-{global_text}",
|
||||
text="action_edit", # keep it (a18n)
|
||||
color="edit",
|
||||
size="normal",
|
||||
hideText=True,
|
||||
iconName="pen",
|
||||
iconColor="white",
|
||||
attrs={
|
||||
"data-display-index": display_index,
|
||||
"data-display-group": "main",
|
||||
},
|
||||
),
|
||||
button_widget(
|
||||
id=f"delete-user-{filename}-{global_text}",
|
||||
text="action_delete", # keep it (a18n)
|
||||
color="error",
|
||||
size="normal",
|
||||
hideText=True,
|
||||
iconName="trash",
|
||||
iconColor="white",
|
||||
modal={
|
||||
"widgets": [
|
||||
title_widget(title="user_management_delete_title"), # keep it (a18n)
|
||||
text_widget(text="user_management_delete_global_subtitle" if is_global else "user_management_delete_no_global_subtitle"), # keep it (a18n)
|
||||
text_widget(bold=True, text=filename),
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"close-delete-btn-{filename}-{global_text}",
|
||||
text="action_close", # keep it (a18n)
|
||||
color="close",
|
||||
size="normal",
|
||||
attrs={"data-close-modal": ""}, # a11y
|
||||
),
|
||||
button_widget(
|
||||
id=f"delete-btn-{filename}-{global_text}",
|
||||
text="action_delete", # keep it (a18n)
|
||||
color="delete",
|
||||
size="normal",
|
||||
iconName="trash",
|
||||
iconColor="white",
|
||||
attrs={
|
||||
"data-submit-form": f"""{{ "filename" : "{filename}", "is_global" : "{global_text}" }}""",
|
||||
"data-submit-endpoint": "/delete",
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
services_items = []
|
||||
# get id
|
||||
for index, service in enumerate(services):
|
||||
services_items.append(
|
||||
{
|
||||
"id": text_widget(text=index)["data"],
|
||||
"name": text_widget(text=service)["data"],
|
||||
}
|
||||
)
|
||||
|
||||
services_detail = (
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"services-btn-{filename}-{global_text}",
|
||||
type="button",
|
||||
iconName="disk",
|
||||
iconColor="white",
|
||||
text="configs_show_services", # keep it (a18n)
|
||||
color="orange",
|
||||
size="normal",
|
||||
modal={
|
||||
"widgets": [
|
||||
title_widget(title="configs_services_title"), # keep it (a18n)
|
||||
text_widget(text="configs_services_subtitle"), # keep it (a18n)
|
||||
tabulator_widget(
|
||||
id=f"table-services-{filename}-{global_text}",
|
||||
columns=[{"title": "id", "field": "id", "formatter": "text"}, {"title": "Name", "field": "name", "formatter": "text"}],
|
||||
# Add every services that apply to the conf. All if global.
|
||||
items=services_items,
|
||||
filters=[
|
||||
{
|
||||
"type": "like",
|
||||
"fields": ["name"],
|
||||
"setting": {
|
||||
"id": "input-search-service",
|
||||
"name": "input-search-service",
|
||||
"label": "configs_search_service", # keep it (a18n)
|
||||
"value": "",
|
||||
"inpType": "input",
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"close-services-btn-{filename}-{global_text}",
|
||||
text="action_close", # keep it (a18n)
|
||||
color="close",
|
||||
size="normal",
|
||||
attrs={"data-close-modal": ""}, # a11y
|
||||
)["data"],
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
)["data"]
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
"name": text_widget(text=filename)["data"],
|
||||
"type": text_widget(text=config_type)["data"],
|
||||
"is_global": icons_widget(
|
||||
iconName="check" if is_global == "up" else "cross" if is_global == "down" else "search",
|
||||
value=global_text,
|
||||
)["data"],
|
||||
"actions": {"buttons": actions},
|
||||
}
|
||||
|
||||
|
||||
def configs_form(
|
||||
is_global: bool, # "yes" or "no"
|
||||
filename: str = "",
|
||||
config_type: str = "",
|
||||
types: Optional[list] = None,
|
||||
services: Optional[list] = None,
|
||||
config_value: str = "",
|
||||
is_new: bool = False,
|
||||
display_index: int = 1,
|
||||
) -> dict:
|
||||
global_text = "yes" if is_global else "no"
|
||||
|
||||
return {
|
||||
"type": "card",
|
||||
"maxWidthScreen": "md",
|
||||
"display": ["main", 2],
|
||||
"widgets": [
|
||||
title_widget(
|
||||
title="configs_create_title", # keep it (a18n)
|
||||
),
|
||||
subtitle_widget(
|
||||
subtitle="configs_create_subtitle", # keep it (a18n)
|
||||
),
|
||||
regular_widget(
|
||||
maxWidthScreen="xs",
|
||||
endpoint="/add",
|
||||
fields=[
|
||||
get_fields_from_field(
|
||||
input_widget(
|
||||
id="instance-name",
|
||||
name="instance-name",
|
||||
label="configs_name", # keep it (a18n)
|
||||
value="",
|
||||
pattern="", # add your pattern if needed
|
||||
columns={"pc": 12, "tablet": 12, "mobile": 12},
|
||||
placeholder="configs_name_placeholder", # keep it (a18n)
|
||||
popovers=[
|
||||
{
|
||||
"iconName": "info",
|
||||
"text": "configs_name_desc",
|
||||
}
|
||||
],
|
||||
)
|
||||
),
|
||||
get_fields_from_field(
|
||||
input_widget(
|
||||
id="instance-hostname",
|
||||
name="instance-hostname",
|
||||
label="configs_hostname", # keep it (a18n)
|
||||
value="",
|
||||
pattern="", # add your pattern if needed
|
||||
columns={"pc": 12, "tablet": 12, "mobile": 12},
|
||||
placeholder="configs_hostname_placeholder", # keep it (a18n)
|
||||
popovers=[
|
||||
{
|
||||
"iconName": "info",
|
||||
"text": "configs_hostname_desc",
|
||||
}
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
buttons=[
|
||||
button_widget(
|
||||
id="create-instance-submit",
|
||||
text="action_create",
|
||||
iconName="plus",
|
||||
iconColor="white",
|
||||
color="success",
|
||||
size="normal",
|
||||
type="submit",
|
||||
containerClass="flex justify-center",
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def configs_tabs():
|
||||
return {
|
||||
"type": "tabs",
|
||||
"widgets": [
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
text="configs_tab_list",
|
||||
display=["main", 1],
|
||||
size="tab",
|
||||
color="info",
|
||||
iconColor="white",
|
||||
iconName="list",
|
||||
),
|
||||
button_widget(
|
||||
text="configs_tab_add",
|
||||
color="success",
|
||||
display=["main", 2],
|
||||
size="tab",
|
||||
iconColor="white",
|
||||
iconName="plus",
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def configs_list(instances: Optional[list] = None, types: Optional[list] = None, methods: Optional[list] = None, healths: Optional[list] = None) -> dict:
|
||||
if instances is None or len(instances) == 0:
|
||||
return {
|
||||
"type": "card",
|
||||
"gridLayoutClass": "transparent",
|
||||
"widgets": [
|
||||
unmatch_widget(text="configs_not_found"),
|
||||
],
|
||||
}
|
||||
|
||||
items = []
|
||||
|
||||
for instance in instances:
|
||||
items.append(
|
||||
instance_item(
|
||||
instance_name=instance.get("name", ""),
|
||||
hostname=instance.get("hostname", ""),
|
||||
instance_type=instance.get("type", ""),
|
||||
method=instance.get("method", ""),
|
||||
health=instance.get("health", ""),
|
||||
creation_date=instance.get("creation_date", ""),
|
||||
last_seen=instance.get("last_seen", ""),
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"type": "card",
|
||||
"display": ["main", 1],
|
||||
"widgets": [
|
||||
title_widget(
|
||||
title="configs_list_title", # keep it (a18n)
|
||||
),
|
||||
subtitle_widget(
|
||||
subtitle="configs_list_subtitle", # keep it (a18n)
|
||||
),
|
||||
tabulator_widget(
|
||||
id="table-instances",
|
||||
columns=columns,
|
||||
items=items,
|
||||
filters=configs_filter(types=types, methods=methods, healths=healths),
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def configs_builder(instances: Optional[list] = None, types: Optional[list] = None, methods: Optional[list] = None, healths: Optional[list] = None) -> list:
|
||||
return [
|
||||
# Tabs is button group with display value and a size tab inside a tabs container
|
||||
configs_tabs(),
|
||||
configs_list(instances=instances, types=types, methods=methods, healths=healths),
|
||||
configs_new_form(),
|
||||
]
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
from .utils.widgets import (
|
||||
input_widget,
|
||||
select_widget,
|
||||
checkbox_widget,
|
||||
combobox_widget,
|
||||
editor_widget,
|
||||
button_widget,
|
||||
button_group_widget,
|
||||
title_widget,
|
||||
text_widget,
|
||||
tabulator_widget,
|
||||
icons_widget,
|
||||
)
|
||||
from typing import Optional
|
||||
from .utils.table import add_column
|
||||
|
||||
|
||||
configs_columns = [
|
||||
add_column(title="Name", field="name", formatter="text"),
|
||||
add_column(title="Type", field="type", formatter="text"),
|
||||
add_column(title="global", field="global", formatter="icons"),
|
||||
add_column(
|
||||
title="services", field="services", formatter="buttonGroup"
|
||||
), # We will display a button with a modal that show all services apply. Case global, show all services.
|
||||
add_column(
|
||||
title="actions", field="actions", formatter="buttonGroup"
|
||||
), # edit button that will switch to the form using display store + delete with modal to confirm
|
||||
]
|
||||
|
||||
|
||||
def config_form(
|
||||
is_global: str, # "yes" or "no"
|
||||
filename: str = "",
|
||||
config_type: str = "",
|
||||
types: Optional[list] = None,
|
||||
services: Optional[list] = None,
|
||||
config_value: str = "",
|
||||
is_new: bool = False,
|
||||
display_index: int = 1,
|
||||
):
|
||||
return (
|
||||
{
|
||||
"type": "card",
|
||||
"display": ["main", display_index], # Allow to toggle between each form using displayStore
|
||||
"widgets": [
|
||||
button_widget(
|
||||
id=f"back-btn-{filename}-global-{is_global}" if not is_new else "back-btn",
|
||||
text="action_back", # keep it (a18n)
|
||||
color="info",
|
||||
size="normal",
|
||||
display=["main", 0] if not is_new else [], # Return to list if edit
|
||||
),
|
||||
input_widget(
|
||||
id=f"config-name-new" if is_new else f"config-name-{filename}-global-{is_global}",
|
||||
name="config-name",
|
||||
label="configs_filename", # keep it (a18n)
|
||||
value="" if is_new else filename, # empty if new or replace by the filename value to edit (.conf excluded)
|
||||
pattern="", # add your pattern if needed
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
),
|
||||
# Select between available types
|
||||
select_widget(
|
||||
id="select-type",
|
||||
name="select-type",
|
||||
label="configs_types", # keep it (a18n)
|
||||
value="" if is_new else config_type,
|
||||
values=types or [], # set all available types like ["http", "modsec"]
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
onlyDown=True,
|
||||
),
|
||||
# Add script on Page.vue to disabled listcheck in case checkbox is checked
|
||||
# This checkbox is priority over services checklist
|
||||
# Check or not to used globally the conf
|
||||
checkbox_widget(
|
||||
id="config-global",
|
||||
name="config-global",
|
||||
label="configs_global", # keep it (a18n)
|
||||
value=is_global, # no if new, else it depends of the current conf
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
),
|
||||
# Case checkbox is checked, this checklist will be ignored on server
|
||||
# Combobox ATM but will be replace by a checklist
|
||||
# set services list ATM, we will update by a checklist with [{value : "service1", is_check : bool}, ...]
|
||||
combobox_widget(
|
||||
id="combo-services",
|
||||
name="combo-services",
|
||||
label="configs_types", # keep it (a18n)
|
||||
value="",
|
||||
values=services or [], # set services list ATM, we will update by a checklist with [{value : "service1", is_check : bool}, ...]
|
||||
onlyDown=True,
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
),
|
||||
# Editor to edit the conf
|
||||
editor_widget(
|
||||
id="config-value",
|
||||
name="config-value",
|
||||
label="configs_value", # keep it (a18n)
|
||||
value="" if is_new else config_value,
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
),
|
||||
input_widget(
|
||||
id="operation",
|
||||
name="operation",
|
||||
label="configs_operation", # keep it (a18n)
|
||||
value="new" if is_new else "edit",
|
||||
pattern="", # add your pattern if needed
|
||||
columns={"pc": 3, "tablet": 4, "mobile": 12},
|
||||
inpClass="hidden", # hide it
|
||||
),
|
||||
button_widget(
|
||||
id="update-config",
|
||||
text="action_create" if is_new else "action_update", # keep it (a18n)
|
||||
color="success",
|
||||
size="normal",
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def configs_filter(types: list):
|
||||
return [
|
||||
{
|
||||
"type": "like",
|
||||
"fields": ["name"],
|
||||
"setting": {
|
||||
"id": "input-search-name",
|
||||
"name": "input-search-name",
|
||||
"label": "configs_search_name", # keep it (a18n)
|
||||
"value": "",
|
||||
"inpType": "input",
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "=",
|
||||
"fields": ["type"],
|
||||
"setting": {
|
||||
"id": "select-type",
|
||||
"name": "select-type",
|
||||
"label": "configs_select_type", # keep it (a18n)
|
||||
"value": "all", # keep "all"
|
||||
"values": ["all"] + types,
|
||||
"inpType": "select",
|
||||
"onlyDown": True,
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "=",
|
||||
"fields": ["global"],
|
||||
"setting": {
|
||||
"id": "select-global",
|
||||
"name": "select-global",
|
||||
"label": "configs_select_global", # keep it (a18n)
|
||||
"value": "all", # keep "all"
|
||||
"values": ["all", "yes", "no"], # keep
|
||||
"inpType": "select",
|
||||
"onlyDown": True,
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def config_item(filename: str, conf_type: str, is_global: str, services: list, display_index: int):
|
||||
|
||||
services_items = []
|
||||
# get id
|
||||
for index, service in enumerate(services):
|
||||
services_items.append(
|
||||
{
|
||||
"id": text_widget(text=index)["data"],
|
||||
"name": text_widget(text=service)["data"],
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
{
|
||||
"name": text_widget(text=filename)["data"],
|
||||
"type": text_widget(text=conf_type)["data"],
|
||||
"global": icons_widget(iconName="check" if is_global == "yes" else "cross", value=is_global)["data"],
|
||||
"services": button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"services-btn-{filename}-global-{is_global}",
|
||||
type="button",
|
||||
iconName="disk",
|
||||
iconColor="white",
|
||||
text="configs_show_services", # keep it (a18n)
|
||||
color="orange",
|
||||
size="normal",
|
||||
modal={
|
||||
"widgets": [
|
||||
title_widget(title="configs_services_title"), # keep it (a18n)
|
||||
text_widget(text="configs_services_subtitle"), # keep it (a18n)
|
||||
tabulator_widget(
|
||||
id=f"table-services-{filename}-global-{is_global}",
|
||||
columns=[{"title": "id", "field": "id", "formatter": "text"}, {"title": "Name", "field": "name", "formatter": "text"}],
|
||||
# Add every services that apply to the conf. All if global.
|
||||
items=services_items,
|
||||
filters=[
|
||||
{
|
||||
"type": "like",
|
||||
"fields": ["name"],
|
||||
"setting": {
|
||||
"id": "input-search-service",
|
||||
"name": "input-search-service",
|
||||
"label": "configs_search_service", # keep it (a18n)
|
||||
"value": "",
|
||||
"inpType": "input",
|
||||
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"close-services-btn-{filename}-global-{is_global}",
|
||||
text="action_close", # keep it (a18n)
|
||||
color="close",
|
||||
size="normal",
|
||||
attrs={"data-close-modal": ""}, # a11y
|
||||
)["data"],
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
)["data"]
|
||||
]
|
||||
),
|
||||
"actions": button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"edit-{filename}-global-{is_global}",
|
||||
type="button",
|
||||
iconName="pen",
|
||||
iconColor="white",
|
||||
text="configs_edit_config", # keep it (a18n)
|
||||
hideText=True,
|
||||
color="yellow",
|
||||
size="normal",
|
||||
display=["main", display_index],
|
||||
)["data"],
|
||||
# Delete button with modal to confirm
|
||||
button_widget(
|
||||
id=f"delete-{filename}-global-{is_global}",
|
||||
type="button",
|
||||
iconName="trash",
|
||||
iconColor="white",
|
||||
text="configs_delete_config", # keep it (a18n)
|
||||
hideText=True,
|
||||
color="error",
|
||||
size="normal",
|
||||
modal={
|
||||
"widgets": [
|
||||
title_widget(title="configs_delete_title"), # keep it (a18n)
|
||||
text_widget(text="configs_delete_subtitle"), # keep it (a18n)
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"close-delete-btn-{filename}-global-{is_global}",
|
||||
text="action_close", # keep it (a18n)
|
||||
color="close",
|
||||
size="normal",
|
||||
attrs={"data-close-modal": ""}, # a11y
|
||||
)["data"],
|
||||
]
|
||||
),
|
||||
button_widget(
|
||||
id=f"delete-btn-{filename}-global-{is_global}",
|
||||
text="action_delete", # keep it (a18n)
|
||||
color="delete",
|
||||
size="normal",
|
||||
attrs={
|
||||
"data-submit-form": f"""{{"conf_name" : "{filename}-global-{is_global}", "conf_type" : "{conf_type}" }}""",
|
||||
"data-submit-endpoint": "/delete",
|
||||
},
|
||||
)["data"],
|
||||
],
|
||||
},
|
||||
)["data"],
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def configs_builder(configs: list, config_types) -> list:
|
||||
configs_items = []
|
||||
configs_forms = []
|
||||
|
||||
# Start adding the new config form
|
||||
configs_forms.append(config_form(is_new=True, display_index=1, types=config_types, is_global="no"))
|
||||
# display_index start at 2 because 1 is the new and 0 is the configs table
|
||||
for index, config in enumerate(configs):
|
||||
display_index = index + 2
|
||||
configs_items.append(
|
||||
config_item(
|
||||
filename=config.get("filename"),
|
||||
conf_type=config.get("type"),
|
||||
is_global=config.get("is_global"),
|
||||
services=config.get("services"),
|
||||
display_index=display_index,
|
||||
)
|
||||
)
|
||||
configs_forms.append(
|
||||
config_form(
|
||||
filename=config.get("filename"),
|
||||
config_type=config.get("type"),
|
||||
types=config_types,
|
||||
is_global=config.get("is_global"),
|
||||
services=config.get("services"),
|
||||
config_value=config.get("config_value"),
|
||||
display_index=display_index,
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"type": "card",
|
||||
"display": ["main", 0],
|
||||
"widgets": [
|
||||
tabulator_widget(
|
||||
id="table-configs",
|
||||
columns=configs_columns,
|
||||
items=configs_items,
|
||||
filters=configs_filter(config_types),
|
||||
),
|
||||
],
|
||||
},
|
||||
] + configs_forms
|
||||
|
|
@ -178,7 +178,46 @@ def instance_item(
|
|||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
),
|
||||
button_widget(
|
||||
id=f"reload-instance-{instance_name}",
|
||||
text="action_reload", # keep it (a18n)
|
||||
color="success",
|
||||
size="normal",
|
||||
hideText=True,
|
||||
iconName="refresh",
|
||||
iconColor="white",
|
||||
modal={
|
||||
"widgets": [
|
||||
title_widget(title="instances_reload_title"), # keep it (a18n)
|
||||
text_widget(text="instances_reload_subtitle"), # keep it (a18n)
|
||||
text_widget(bold=True, text=instance_name),
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
id=f"close-reload-btn-{instance_name}",
|
||||
text="action_close", # keep it (a18n)
|
||||
color="close",
|
||||
size="normal",
|
||||
attrs={"data-close-modal": ""}, # a11y
|
||||
),
|
||||
button_widget(
|
||||
id=f"reload-btn-{instance_name}",
|
||||
text="action_reload", # keep it (a18n)
|
||||
color="info",
|
||||
size="normal",
|
||||
iconName="globe",
|
||||
iconColor="white",
|
||||
attrs={
|
||||
"data-submit-form": f"""{{"instance_name" : "{instance_name}", "instance_hostname" : "{hostname}" }}""",
|
||||
"data-submit-endpoint": "/reload",
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
if method.strip() in ("ui", "manual", "default"):
|
||||
|
|
@ -381,8 +420,6 @@ def instances_list(instances: Optional[list] = None, types: Optional[list] = Non
|
|||
|
||||
|
||||
def instances_builder(instances: Optional[list] = None, types: Optional[list] = None, methods: Optional[list] = None, healths: Optional[list] = None) -> list:
|
||||
items = []
|
||||
|
||||
return [
|
||||
# Tabs is button group with display value and a size tab inside a tabs container
|
||||
instances_tabs(),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
from utils import save_builder
|
||||
|
||||
from pages.configs2 import configs_builder
|
||||
from pages.configs import configs_builder
|
||||
|
||||
|
||||
configs = [
|
||||
|
|
@ -28,4 +28,4 @@ config_types = ["http", "https", "socks4", "socks5"]
|
|||
|
||||
builder = configs_builder(configs, config_types)
|
||||
|
||||
save_builder("configs2", builder, update_page=False)
|
||||
save_builder(page_name="configs", output=builder, script_name="configs")
|
||||
86
src/ui/client/dashboard/components/Builder/Configs.vue
Normal file
86
src/ui/client/dashboard/components/Builder/Configs.vue
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<script setup>
|
||||
// Containers
|
||||
import Grid from "@components/Widget/Grid.vue";
|
||||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
import Tabulator from "@components/Widget/Tabulator.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import Regular from "@components/Form/Regular.vue";
|
||||
import Title from "@components/Widget/Title.vue";
|
||||
import Subtitle from "@components/Widget/Subtitle.vue";
|
||||
import MessageUnmatch from "@components/Message/Unmatch.vue";
|
||||
import { useEqualStr } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
* @name Builder/Configs.vue
|
||||
* @description This component is lightweight builder containing only the necessary components to create the configs page.
|
||||
* @example
|
||||
* [
|
||||
* {
|
||||
* type: "card",
|
||||
* gridLayoutClass: "transparent",
|
||||
* widgets: [
|
||||
* { type: "MessageUnmatch",
|
||||
* data: { text: "bans_not_found" }
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* ];
|
||||
* @param {array} builder - Array of containers and widgets
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
builder: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- top level grid (layout) -->
|
||||
<GridLayout
|
||||
v-for="(container, index) in props.builder"
|
||||
:key="index"
|
||||
:gridLayoutClass="container.containerClass"
|
||||
:maxWidthScreen="container.maxWidthScreen"
|
||||
:type="container.type"
|
||||
:title="container.title"
|
||||
:link="container.link"
|
||||
:columns="container.containerColumns"
|
||||
:id="container.id"
|
||||
:display="container.display"
|
||||
>
|
||||
<!-- widget grid -->
|
||||
<Grid>
|
||||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<Title v-if="useEqualStr(widget.type, 'Title')" v-bind="widget.data" />
|
||||
<Subtitle
|
||||
v-if="useEqualStr(widget.type, 'Subtitle')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<MessageUnmatch
|
||||
v-if="useEqualStr(widget.type, 'MessageUnmatch')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Tabulator
|
||||
v-if="useEqualStr(widget.type, 'Tabulator')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Button
|
||||
v-if="useEqualStr(widget.type, 'Button')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Regular
|
||||
v-if="useEqualStr(widget.type, 'Regular')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<ButtonGroup
|
||||
v-if="useEqualStr(widget.type, 'ButtonGroup')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
</template>
|
||||
|
|
@ -9,7 +9,7 @@ import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
|||
import { useEqualStr } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
* @name Builder/PLugin.vue
|
||||
* @name Builder/PLugins.vue
|
||||
* @description This component is lightweight builder containing only the necessary components to create the plugins page.
|
||||
* @example
|
||||
* [
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import MessageUnmatch from "@components/Message/Unmatch.vue";
|
|||
import { useEqualStr } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
* @name Builder/Bans.vue
|
||||
* @description This component is lightweight builder containing only the necessary components to create the bans page.
|
||||
* @name Builder/UserManagement.vue
|
||||
* @description This component is lightweight builder containing only the necessary components to create the user management page.
|
||||
* @example
|
||||
* [
|
||||
* {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const icon = reactive({
|
|||
:data-color="icon.color"
|
||||
:data-value="props.value"
|
||||
:aria-disabled="props.disabled ? 'true' : 'false'"
|
||||
data-svg="box"
|
||||
data-svg="back"
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
:class="[props.iconClass, icon.color, 'fill dark:brightness-[125%]']"
|
||||
|
|
|
|||
58
src/ui/client/dashboard/components/Icons/Refresh.vue
Normal file
58
src/ui/client/dashboard/components/Icons/Refresh.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script setup>
|
||||
import { defineProps, reactive } from "vue";
|
||||
/**
|
||||
* @name Icons/Refresh.vue
|
||||
* @description This component is a svg icon representing refresh.
|
||||
* @example
|
||||
* {
|
||||
* color: 'info',
|
||||
* }
|
||||
* @param {String} [iconClass="icon-default"] - The class of the icon.
|
||||
* @param {Any} [value=""] - Attach a value to icon. Useful on some cases like table filtering using icons.
|
||||
* @param {String} [color="dark"] - The color of the icon between some tailwind css available colors (purple, green, red, orange, blue, yellow, gray, dark, amber, emerald, teal, indigo, cyan, sky, pink...). Darker colors are also available using the base color and adding '-darker' (e.g. 'red-darker').
|
||||
* @param {Boolean} [disabled=false] - If true, the icon will be disabled.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "icon-default",
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "sky",
|
||||
},
|
||||
disabled: { type: Boolean, required: false, default: false },
|
||||
});
|
||||
|
||||
const icon = reactive({
|
||||
color: props.color || "sky",
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<svg
|
||||
:data-color="icon.color"
|
||||
:data-value="props.value"
|
||||
:aria-disabled="props.disabled ? 'true' : 'false'"
|
||||
data-svg="reload"
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
:class="[props.iconClass, icon.color, 'fill dark:brightness-[125%]']"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -39,6 +39,7 @@ import Document from "@components/Icons/Document.vue";
|
|||
import Eye from "@components/Icons/Eye.vue";
|
||||
import Uncheck from "@components/Icons/Uncheck.vue";
|
||||
import Back from "@components/Icons/Back.vue";
|
||||
import Refresh from "@components/Icons/Refresh.vue";
|
||||
|
||||
/**
|
||||
* @name Widget/Icons.vue
|
||||
|
|
@ -111,6 +112,7 @@ onMounted(() => {
|
|||
v-if="useEqualStr(props.iconName, 'exclamation')"
|
||||
v-bind="icon"
|
||||
/>
|
||||
<Refresh v-if="useEqualStr(props.iconName, 'refresh')" v-bind="icon" />
|
||||
<Back v-if="useEqualStr(props.iconName, 'back')" v-bind="icon" />
|
||||
<Box v-if="useEqualStr(props.iconName, 'box')" v-bind="icon" />
|
||||
<Uncheck v-if="useEqualStr(props.iconName, 'uncheck')" v-bind="icon" />
|
||||
|
|
|
|||
|
|
@ -206,6 +206,8 @@
|
|||
"instances_method_popover": "Select the instance method.",
|
||||
"instances_health": "health",
|
||||
"instances_health_popover": "Select the instance health. Health is based on the last seen date.",
|
||||
"instances_reload_title": "Reload instance",
|
||||
"instances_reload_subtitle": "Are you sure to reload the following instance ?",
|
||||
"instances_ping_title": "Ping instance",
|
||||
"instances_ping_subtitle": "Ping will check instance health of the following instance.",
|
||||
"instances_delete_title": "Delete instance",
|
||||
|
|
|
|||
44
src/ui/client/dashboard/pages/configs/Configs.vue
Normal file
44
src/ui/client/dashboard/pages/configs/Configs.vue
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount, onMounted } from "vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
import BuilderConfigs from "@components/Builder/Configs.vue";
|
||||
import { useGlobal } from "@utils/global.js";
|
||||
import { useDisplayStore } from "@store/global.js";
|
||||
|
||||
/**
|
||||
* @name Page/Configs.vue
|
||||
* @description This component is the configs page.
|
||||
This page displays current users and allows to manage them.
|
||||
We are using displayStore and setting ["main", 1] to display the instances list first.
|
||||
*/
|
||||
|
||||
// Set default store
|
||||
const displayStore = useDisplayStore();
|
||||
displayStore.setDisplay("main", 0);
|
||||
|
||||
const configs = reactive({
|
||||
builder: "",
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
// Get builder data
|
||||
const dataAtt = "data-server-builder";
|
||||
const dataEl = document.querySelector(`[${dataAtt}]`);
|
||||
const data =
|
||||
dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt)
|
||||
? JSON.parse(atob(dataEl.getAttribute(dataAtt)))
|
||||
: {};
|
||||
configs.builder = data;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// Set the page title
|
||||
useGlobal();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout>
|
||||
<BuilderConfigs v-if="configs.builder" :builder="configs.builder" />
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
11
src/ui/client/dashboard/pages/configs/configs.js
Normal file
11
src/ui/client/dashboard/pages/configs/configs.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { getI18n } from "@utils/lang.js";
|
||||
import Configs from "./Configs.vue";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(Configs)
|
||||
.use(pinia)
|
||||
.use(getI18n(["dashboard", "action", "inp", "icons", "configs"]))
|
||||
.mount("#app");
|
||||
28
src/ui/client/dashboard/pages/configs/index.html
Normal file
28
src/ui/client/dashboard/pages/configs/index.html
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -29,93 +29,6 @@ onMounted(() => {
|
|||
// Set the page title
|
||||
useGlobal();
|
||||
});
|
||||
// const data = [
|
||||
// {
|
||||
// type: "card",
|
||||
// link : "https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui"
|
||||
// containerColumns: { pc: 4, tablet: 6, mobile: 12 },
|
||||
// widgets: [
|
||||
// {
|
||||
// type: "Stat",
|
||||
// data: {
|
||||
// title: "home_version",
|
||||
// subtitle: "home_all_features_available" if is_pro_version else "home_upgrade_pro",
|
||||
// subtitleColor: "success" is is_pro_version else "warning",
|
||||
// stat: "home_pro" if is_pro_version else "home_free",
|
||||
// iconName: "crown" if is_pro_version else "core",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// 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_latest_version" if is_latest_version else "home_upgrade_available",
|
||||
// subtitleColor: "success" if is_latest_version else "warning",
|
||||
// stat: <current_version>,
|
||||
// iconName: "wire",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// 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: "<instances_total>",
|
||||
// iconName: "box",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// 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: "<services_total>",
|
||||
// iconName: "disk",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// type: "card",
|
||||
// link: "/plugins",
|
||||
// containerColumns: { pc: 4, tablet: 6, mobile: 12 },
|
||||
// widgets: [
|
||||
// {
|
||||
// type: "Stat",
|
||||
// data: {
|
||||
// title: "home_plugins",
|
||||
// subtitle: "home_no_error" if all_plugins_ok else "home_errors_found",
|
||||
// subtitleColor: "success" if all_plugins_ok else "error",
|
||||
// stat: "<plugins_total>",
|
||||
// iconName: "puzzle",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -35,28 +35,6 @@ onMounted(() => {
|
|||
// Set the page title
|
||||
useGlobal();
|
||||
});
|
||||
// const data = [
|
||||
// {
|
||||
// type: "Instance",
|
||||
// data: {
|
||||
// details: [
|
||||
// { key: <instances_hostname="hostname">, value: "www.example.com" },
|
||||
// { key: <instances_method="method">, value: <dashboard_ui> or <dashboard_scheduler>...},
|
||||
// { key: <instances_port="port">, value: "1084" },
|
||||
// { key: <instances_status="status">, value: <instances_active="active"> or <instances_inactive="inactive"> },
|
||||
// ],
|
||||
// status: "success",
|
||||
// title: "www.example.com",
|
||||
// buttons: [
|
||||
// {
|
||||
// text: <action_*>,
|
||||
// color: "edit",
|
||||
// size: "normal",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -35,28 +35,6 @@ onMounted(() => {
|
|||
// Set the page title
|
||||
useGlobal();
|
||||
});
|
||||
// const data = [
|
||||
// {
|
||||
// type: "Instance",
|
||||
// data: {
|
||||
// details: [
|
||||
// { key: <instances_hostname="hostname">, value: "www.example.com" },
|
||||
// { key: <instances_method="method">, value: <dashboard_ui> or <dashboard_scheduler>...},
|
||||
// { key: <instances_port="port">, value: "1084" },
|
||||
// { key: <instances_status="status">, value: <instances_active="active"> or <instances_inactive="inactive"> },
|
||||
// ],
|
||||
// status: "success",
|
||||
// title: "www.example.com",
|
||||
// buttons: [
|
||||
// {
|
||||
// text: <action_*>,
|
||||
// color: "edit",
|
||||
// size: "normal",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue