update components, style, format, utils, pages

* update fields error message handling
* fix form error displaying even if no error with empty data
* user management format done : handle no roles, handle no users, separate filter roles from roles form
* add back button style + global button enhance
* add i18n user management
* remove style width logic from select type (now useless)
* update layout style to be more standard
* misc builder pages enhancement
This commit is contained in:
Jordan Blasenhauer 2024-08-14 21:01:53 +02:00
parent 98bc4f1124
commit e218f8e621
38 changed files with 1828 additions and 1107 deletions

View file

@ -112,10 +112,10 @@ def bans_filters(reasons: Optional[list] = None, remains: Optional[list] = None)
"id": "input-search-ip",
"name": "input-search-ip",
"label": "bans_search_ip", # keep it (a18n)
"placeholder": "bans_search_ip_placeholder", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
"isClipboard": True,
"popovers": [
{
@ -123,6 +123,7 @@ def bans_filters(reasons: Optional[list] = None, remains: Optional[list] = None)
"text": "bans_search_ip_desc",
}
],
"fieldSize": "sm",
},
},
]
@ -208,7 +209,7 @@ def bans_list(bans: Optional[list] = None, reasons: Optional[list] = None, remai
id="unselect-all-list",
type="button",
text="action_unselect_all", # keep it (a18n)
color="delete",
color="info",
iconColor="white",
iconName="uncheck",
size="sm",
@ -248,6 +249,8 @@ def bans_list(bans: Optional[list] = None, reasons: Optional[list] = None, remai
text="action_unban", # keep it (a18n)
color="success",
size="normal",
iconName="uncheck",
iconColor="white",
modal={
"widgets": [
title_widget(title="bans_unban_title"), # keep it (a18n)
@ -265,6 +268,8 @@ def bans_list(bans: Optional[list] = None, reasons: Optional[list] = None, remai
id="unban-btn-confirm",
text="action_unban", # keep it (a18n)
color="success",
iconName="uncheck",
iconColor="white",
size="normal",
attrs={"data-unban": ""},
),
@ -361,6 +366,8 @@ def bans_add() -> dict:
type="button",
text="action_save", # keep it (a18n)
color="success",
iconName="plus",
iconColor="white",
size="normal",
)
]

View file

@ -35,6 +35,7 @@ def instances_filter(healths: str, types: Optional[list] = None, methods: Option
"id": "input-search-host-name",
"name": "input-search-host-name",
"label": "instances_search", # keep it (a18n)
"placeholder": "instances_search_placeholder", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
@ -69,6 +70,7 @@ def instances_filter(healths: str, types: Optional[list] = None, methods: Option
"text": "instances_type_popover",
}
],
"fieldSize": "sm",
},
}
)
@ -93,6 +95,7 @@ def instances_filter(healths: str, types: Optional[list] = None, methods: Option
"text": "instances_method_popover",
}
],
"fieldSize": "sm",
},
},
)
@ -117,6 +120,7 @@ def instances_filter(healths: str, types: Optional[list] = None, methods: Option
"text": "instances_health_popover",
}
],
"fieldSize": "sm",
},
},
)
@ -163,6 +167,8 @@ def instance_item(
text="action_ping", # 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": "/ping",
@ -204,6 +210,8 @@ def instance_item(
text="action_delete", # keep it (a18n)
color="delete",
size="normal",
iconName="trash",
iconColor="white",
attrs={
"data-submit-form": f"""{{ "instance_name" : "{instance_name}", "instance_hostname" : "{hostname}" }}""",
"data-submit-endpoint": "/delete",
@ -255,6 +263,13 @@ def instances_new_form() -> dict:
value="",
pattern="", # add your pattern if needed
columns={"pc": 12, "tablet": 12, "mobile": 12},
placeholder="instances_name_placeholder", # keep it (a18n)
popovers=[
{
"iconName": "info",
"text": "instances_name_desc",
}
],
)
),
get_fields_from_field(
@ -265,13 +280,22 @@ def instances_new_form() -> dict:
value="",
pattern="", # add your pattern if needed
columns={"pc": 12, "tablet": 12, "mobile": 12},
placeholder="instances_hostname_placeholder", # keep it (a18n)
popovers=[
{
"iconName": "info",
"text": "instances_hostname_desc",
}
],
)
),
],
buttons=[
button_widget(
id="create-instance-submit",
text="action_save",
text="action_create",
iconName="plus",
iconColor="white",
color="success",
size="normal",
type="submit",

View file

@ -55,6 +55,7 @@ def jobs_builder(jobs):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
{
@ -77,6 +78,7 @@ def jobs_builder(jobs):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
{
@ -99,6 +101,7 @@ def jobs_builder(jobs):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
{
@ -121,6 +124,7 @@ def jobs_builder(jobs):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
],

View file

@ -26,6 +26,7 @@ def reports_filters(reasons: Optional[list] = None, countries: Optional[list] =
"id": "input-search-misc",
"name": "input-search-misc",
"label": "reports_search_misc", # keep it (a18n)
"placeholder": "reports_search_misc_placeholder", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},

View file

@ -65,6 +65,7 @@ def services_builder(services):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
{
@ -87,6 +88,7 @@ def services_builder(services):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
{
@ -109,6 +111,7 @@ def services_builder(services):
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"fieldSize": "sm",
},
},
],

View file

@ -0,0 +1,460 @@
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,
select_widget,
datepicker_widget,
)
from .utils.table import add_column
from .utils.format import get_fields_from_field
from typing import Optional, Union
# - created_method
# - is_superadmin
# - role
# - role_description
# - permissions (liste of permissions [])
# - creation_date
# - last_update (last time update user info)
users_columns = [
add_column(title="Username", field="username", formatter="text"),
add_column(title="Email", field="email", formatter="text"),
add_column(title="Role", field="role", formatter="text"), # superadmin, admin, writer...
add_column(title="Totp", field="is_totp", formatter="icons"),
add_column(title="Creation date", field="creation_date", formatter="fields", minWidth=250), # datepicker
add_column(title="Last login", field="last_login", formatter="fields", minWidth=250), # datepicker
add_column(title="Last update", field="last_update", formatter="fields", minWidth=250), # datepicker
add_column(title="Actions", field="actions", formatter="buttongroup"),
]
def users_filter(roles: Optional[list] = None, totp_states: Optional[list] = None) -> list: # healths = "up", "down", "loading"
filters = [
{
"type": "like",
"fields": ["username", "email"],
"setting": {
"id": "input-search-username-email",
"name": "input-search-username-email",
"label": "user_management_search", # keep it (a18n)
"placeholder": "user_management_search_placeholder", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"popovers": [
{
"iconName": "info",
"text": "user_management_search_desc",
}
],
"fieldSize": "sm",
},
}
]
if roles is not None and len(roles) >= 2:
filters.append(
{
"type": "=",
"fields": ["role"],
"setting": {
"id": "select-role",
"name": "select-role",
"label": "user_management_select_role", # keep it (a18n)
"value": "all", # keep "all"
"values": ["all"] + roles,
"inpType": "select",
"onlyDown": True,
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"popovers": [
{
"iconName": "info",
"text": "user_management_select_role_desc",
}
],
"fieldSize": "sm",
},
}
)
if totp_states is not None and len(totp_states) >= 2:
filters.append(
{
"type": "=",
"fields": ["is_totp"],
"setting": {
"id": "select-is-totp",
"name": "select-is-totp",
"label": "user_management_select_is_totp", # keep it (a18n)
"value": "all", # keep "all"
"values": ["all"] + totp_states,
"inpType": "select",
"onlyDown": True,
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
"popovers": [
{
"iconName": "info",
"text": "user_management_select_is_totp_desc",
}
],
"fieldSize": "sm",
},
},
)
return filters
def user_management_item(
id: Union[str, int],
username: str,
email: str,
role: str,
totp_state: str,
last_login: int,
creation_date: int,
last_update: int,
display_index: Union[str, int],
):
actions = []
# Can edit or delete only if not super admin
if not "super" in role:
actions = [
button_widget(
id=f"edit-user-{username}",
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-{username}",
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_subtitle"), # keep it (a18n)
text_widget(bold=True, text=username),
button_group_widget(
buttons=[
button_widget(
id=f"close-delete-btn-{username}",
text="action_close", # keep it (a18n)
color="close",
size="normal",
attrs={"data-close-modal": ""}, # a11y
),
button_widget(
id=f"delete-btn-{username}",
text="action_delete", # keep it (a18n)
color="delete",
size="normal",
iconName="trash",
iconColor="white",
attrs={
"data-submit-form": f"""{{ "username" : "{username}" }}""",
"data-submit-endpoint": "/delete",
},
),
]
),
],
},
),
]
return {
"username": text_widget(text=username)["data"],
"email": text_widget(text=email)["data"],
"role": text_widget(text=role)["data"],
"is_totp": icons_widget(
iconName="check" if totp_state == "enable" else "cross" if totp_state == "disable" else "search",
value=totp_state,
)["data"],
"creation_date": get_fields_from_field(
datepicker_widget(
id=f"datepicker-user-creation-{id}",
name=f"datepicker-user-creation-{id}",
label="user_management_creation_date", # keep it (a18n)
hideLabel=True,
value=creation_date,
disabled=True, # Readonly
columns={"pc": 12, "tablet": 12, "mobile": 12},
)
),
"last_login": get_fields_from_field(
datepicker_widget(
id=f"datepicker-user-last-login-{id}",
name=f"datepicker-user-last-login-{id}",
label="user_management_last_login_date", # keep it (a18n)
hideLabel=True,
value=last_login,
disabled=True, # Readonly
columns={"pc": 12, "tablet": 12, "mobile": 12},
)
),
"last_update": get_fields_from_field(
datepicker_widget(
id=f"datepicker-user-last-update-{id}",
name=f"datepicker-user-last-update-{id}",
label="user_management_last_update_date", # keep it (a18n)
hideLabel=True,
value=last_update,
disabled=True, # Readonly
columns={"pc": 12, "tablet": 12, "mobile": 12},
)
),
"actions": {"buttons": actions},
}
def user_management_form(
username: str,
role: str,
display_index: Union[str, int],
roles: Optional[list] = None,
is_new: bool = False,
) -> dict:
# Always main action button but back button is only for edit
buttons = []
if not is_new:
buttons.append(
button_widget(
id=f"back-from-create-user-{'new' if is_new else username}",
text="action_back",
color="back",
iconName="back",
size="normal",
type="button",
attrs={
"data-display-index": 0,
"data-display-group": "main",
},
),
)
buttons.append(
button_widget(
id=f"create-user-submit-{'new' if is_new else username}" if is_new else f"edit-user-submit-{'new' if is_new else username}",
text="action_create" if is_new else "action_edit",
iconName="plus" if is_new else "pen",
color="success" if is_new else "edit",
iconColor="white",
size="normal",
type="submit",
),
)
return {
"type": "card",
"maxWidthScreen": "md",
"display": ["main", display_index],
"widgets": [
title_widget(
title="user_management_create_title" if is_new else "user_management_edit_title", # keep it (a18n)
),
subtitle_widget(
subtitle="user_management_create_subtitle" if is_new else "user_management_edit_subtitle", # keep it (a18n)
),
regular_widget(
maxWidthScreen="xs",
endpoint="/add" if is_new else "/edit",
fields=[
get_fields_from_field(
input_widget(
id=f"user-username-{'new' if is_new else username}",
name="username",
label="user_management_form_username", # keep it (a18n)
value="" if is_new else username,
required=True,
placeholder="user_management_form_username_placeholder",
pattern="", # add your pattern if needed
columns={"pc": 12, "tablet": 12, "mobile": 12},
popovers=[
{
"iconName": "info",
"text": "user_management_form_username_desc",
}
],
)
),
get_fields_from_field(
select_widget(
id=f"user-role-{'new' if is_new else username}",
name="role",
label="user_management_form_role", # keep it (a18n)
value="" if is_new else role,
values=roles,
requiredValues=roles,
required=True,
columns={"pc": 12, "tablet": 12, "mobile": 12},
popovers=[
{
"iconName": "info",
"text": "user_management_form_role_desc",
}
],
)
),
get_fields_from_field(
input_widget(
id=f"user-password-{'new' if is_new else username}",
name="password",
label="user_management_form_password" if is_new else "user_management_form_edit_password", # keep it (a18n)
value="",
required=True if is_new else False,
pattern="", # add your pattern if needed
placeholder="user_management_form_password_placeholder" if is_new else "user_management_form_edit_password_placeholder",
columns={"pc": 12, "tablet": 12, "mobile": 12},
popovers=[
{
"iconName": "info",
"text": "user_management_form_password_desc" if is_new else "user_management_form_edit_password_desc",
}
],
)
),
get_fields_from_field(
input_widget(
id=f"user-password-confirm-{'new' if is_new else username}",
name="password-confirm",
label="user_management_form_password_confirm" if is_new else "user_management_form_edit_password_confirm", # keep it (a18n)
value="",
required=True if is_new else False,
pattern="", # add your pattern if needed
placeholder=(
"user_management_form_password_confirm_placeholder" if is_new else "user_management_form_edit_password_confirm_placeholder"
),
columns={"pc": 12, "tablet": 12, "mobile": 12},
)
),
],
buttons=buttons,
),
],
}
def user_management_tabs():
return {
"type": "tabs",
"widgets": [
button_group_widget(
buttons=[
button_widget(
text="user_management_tab_list",
display=["main", 0],
size="tab",
color="info",
iconColor="white",
iconName="list",
),
button_widget(
text="user_management_tab_add",
color="success",
display=["main", 1],
size="tab",
iconColor="white",
iconName="plus",
),
]
)
],
}
def fallback_message(msg: str):
return {
"type": "card",
"gridLayoutClass": "transparent",
"widgets": [
unmatch_widget(text=msg),
],
}
def user_management_builder(
users: Optional[list] = None, roles: Optional[list] = None, roles_form: Optional[list] = None, totp_states: Optional[list] = None
) -> list:
if roles is None or len(roles) == 0 or roles_form is None or len(roles_form) == 0:
return fallback_message("user_management_missing_roles")
users_items = []
users_forms = []
users_forms.append(user_management_form(is_new=True, display_index=1, role="", username="", roles=roles_form))
if users is None or len(users) == 0:
return [
# Tabs is button group with display value and a size tab inside a tabs container
user_management_tabs(),
fallback_message("user_management_users_not_found"),
] + users_forms
# Start adding the new config form
# display_index start at 2 because 1 is the new and 0 is the configs table
for index, user in enumerate(users):
display_index = index + 2
users_items.append(
user_management_item(
id=index,
username=user.get("username"),
email=user.get("email"),
role=user.get("role"),
totp_state=user.get("totp_state"),
last_login=user.get("last_login"),
creation_date=user.get("creation_date"),
last_update=user.get("last_update"),
display_index=display_index,
)
)
users_forms.append(
user_management_form(
username=user.get("username"),
role=user.get("role"),
roles=roles_form,
display_index=display_index,
)
)
return [
# Tabs is button group with display value and a size tab inside a tabs container
user_management_tabs(),
{
"type": "card",
"maxWidthScreen": "3xl",
"display": ["main", 0],
"widgets": [
title_widget(title="user_management_list_title"), # keep it (a18n)
subtitle_widget(subtitle="user_management_list_subtitle"), # keep it (a18n)
tabulator_widget(
id="table-configs",
columns=users_columns,
items=users_items,
layout="fitColumns",
filters=users_filter(roles=roles, totp_states=totp_states),
),
],
},
] + users_forms

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
from utils import save_builder
from pages.user_management import user_management_builder
users = [
{
"username": "username1",
"email": "email1",
"role": "superadmin",
"totp_state": "enable",
"last_login": 1723641467658, # timestamp
"creation_date": 1723641467658, # timestamp
"last_update": 1723641467658, # timestamp
},
{
"username": "username2",
"email": "",
"role": "writer",
"totp_state": "disable",
"last_login": 1723641467658, # timestamp
"creation_date": 1723641467658, # timestamp
"last_update": 1723641467658, # timestamp
},
{
"username": "username3",
"email": "fesfesf",
"role": "reader",
"totp_state": "disable",
"last_login": 1723641467658, # timestamp
"creation_date": 1723641467658, # timestamp
"last_update": 1723641467658, # timestamp
},
{
"username": "username4",
"email": "fesfesfgrd",
"role": "reader",
"totp_state": "enable",
"last_login": 1723641467658, # timestamp
"creation_date": 1723641467658, # timestamp
"last_update": 1723641467658, # timestamp
},
]
roles = ["superadmin", "admin", "writer", "reader"] # add superadmin-like role, this list will be use to filter the roles from list
roles_form = ["admin", "writer", "reader", "all"] # here send only available roles, superadmin is not available for example
totp_state = ["enable", "disable"]
builder = user_management_builder(users=users, roles=roles, totp_states=totp_state, roles_form=roles_form)
save_builder(page_name="usermanagement", output=builder, script_name="usermanagement")

View file

@ -1,301 +0,0 @@
import json
import base64
from builder.utils.widgets import button, button_group, title, text, tabulator, fields, upload, input, combobox, checkbox, select, editor, datepicker
# TODO : REMOVE operation by custom endpoint
def generate_form(
username: str = "",
password: str = "",
email: str = "",
is_new: bool = True,
role: str = "",
roles: list = [],
display_index: int = 1,
):
return (
{
"type": "card",
"display": ["main", display_index], # Allow to toggle between each form using displayStore
"widgets": [
input(
id=f"username-{'new' if is_new else username}",
name="username",
label="users_filename", # keep it (a18n)
value="" if is_new else username,
pattern="", # add your pattern if needed
columns={"pc": 3, "tablet": 4, "mobile": 12},
),
input(
id=f"password-{'new' if is_new else username}",
name="password",
label="users_password", # keep it (a18n)
value="" if is_new else password,
pattern="", # add your pattern if needed
columns={"pc": 3, "tablet": 4, "mobile": 12},
),
# Select between available types
select(
{
"id": "select-role",
"name": "select-role",
"label": "users_role", # keep it (a18n)
"value": role, # current role
"values": roles,
"inpType": "select",
"onlyDown": True,
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
),
input(
id="operation",
name="operation",
label="users_operation", # keep it (a18n)
value="new" if is_new else "edit", # "new" if new or "edit" if edit
pattern="", # add your pattern if needed
columns={"pc": 3, "tablet": 4, "mobile": 12},
inputClass="hidden", # hide it
),
input(
id="old_username",
name="old_username",
label="users_old_username", # keep it (a18n)
value=username, # "new" if new or "edit" if edit
inputClass="hidden", # hide it
),
button(
id="update-user",
text="action_save", # action_new if new or action_edit if edit
color="success",
size="normal",
),
],
},
)
# TODO
def get_forms():
"""We need to generate an empty form for the new conf that will be display index 1.
We need to generate a form for each conf that will be display index > 1 and unique.
"""
forms = []
# Start adding new form with display index 1
# Then we will loop on each conf to add a form with display index > 1 (use loop index)
return forms
users_columns = [
{"title": "Username", "field": "username", "formatter": "text"},
{"title": "Role", "field": "role", "formatter": "text"},
{"title": "Creation date", "field": "creation_date", "formatter": "fields"}, # datepicker
{"title": "Last login", "field": "last_login", "formatter": "fields"}, # datepicker of last login
{"title": "Last login IP", "field": "last_login_ip", "formatter": "fields"}, # datepicker of last login
{"title": "Login count", "field": "login_count", "formatter": "fields"}, # datepicker of last login
{"title": "totp (state)", "field": "totp", "formatter": "icons"}, # icon check or cross
{
"title": "actions",
"field": "actions",
"formatter": "buttonGroup",
}, # edit button that will switch to the form using display store + delete with modal to confirm
]
users_filters = [
{
"type": "like",
"fields": ["username"],
"setting": {
"id": "input-search-username",
"username": "input-search-username",
"label": "users_search_username", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"type": "=",
"fields": ["role"],
"setting": {
"id": "select-role",
"name": "select-role",
"label": "users_select_role", # keep it (a18n)
"value": "all", # keep "all"
"values": ["all", "antibot"], # keep "all" and add your roles
"inpType": "select",
"onlyDown": True,
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"type": "=",
"fields": ["totp"],
"setting": {
"id": "select-totp",
"name": "select-totp",
"label": "users_select_totp", # keep it (a18n)
"value": "all", # keep "all"
"values": ["all", "yes", "no"], # keep
"inpType": "select",
"onlyDown": True,
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
]
users_items = [
{
"username": text(text="Name")["data"], # replace Name by real name
"role": text(text="Role")["data"], # replace Role by real role
"creation_date": datepicker(
id="datepicker-date-id", # replace id by unique id
name="datepicker-date-id", # replace by unique id
label="reports_date", # keep it (a18n)
hideLabel=True,
inputType="datepicker",
value="my_date", # replace my_date by timestamp value
disabled=True, # Readonly
)["data"],
"services": button_group(
buttons=[
button(
id="services-btn-confname", # replace confname by real conf name
type="button",
iconName="disk",
iconColor="white",
text="users_show_services", # keep it (a18n)
color="orange",
size="normal",
modal={
"widgets": [
title(title="users_services_title"), # keep it (a18n)
text(text="users_services_subtitle"), # keep it (a18n)
tabulator(
id="table-services-confname", # replace confname by real conf name
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=[
{
"id": text(text="service_id")["data"],
"name": text(text="service_name")["data"],
}, # replace service_id and service_name by real values
],
filters=[
{
"type": "like",
"fields": ["name"],
"setting": {
"id": "input-search-service",
"name": "input-search-service",
"label": "users_search_service", # keep it (a18n)
"value": "",
"inpType": "input",
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
],
),
button_group(
buttons=[
button(
id="close-services-btn-confname", # replace confname by real conf name
text="action_close", # keep it (a18n)
color="close",
size="normal",
attrs={"data-close-modal": ""}, # a11y
)["data"],
]
),
],
},
)["data"]
]
),
"actions": button_group(
buttons=[
# Need a script at Page.vue level in order to update displayStore when clicking edit button using the data-display attributs
button(
id="edit-confname", # replace confname by real conf name
type="button",
iconName="pen",
iconColor="white",
text="users_edit_config", # keep it (a18n)
hideText=True,
color="yellow",
size="normal",
attrs={"data-display": "display_index"}, # replace by the display index of the related in order to display it
)["data"],
# Delete button with modal to confirm
button(
id="delete-confname", # replace confname by real conf name
type="button",
iconName="trash",
iconColor="white",
text="users_delete_config", # keep it (a18n)
hideText=True,
color="error",
size="normal",
modal={
"widgets": [
title(title="users_delete_title"), # keep it (a18n)
text(text="users_delete_subtitle"), # keep it (a18n)
button_group(
buttons=[
button(
id="close-delete-btn-confname", # replace confname by real conf name
text="action_close", # keep it (a18n)
color="close",
size="normal",
attrs={"data-close-modal": ""}, # a11y
)["data"],
]
),
button(
id="delete-btn-confname", # replace confname by the instance name
text="action_delete", # keep it (a18n)
color="delete",
size="normal",
attrs={
"data-submit-form": '{"conf_name" : "", "conf_type" : "", "operation" : "delete" }'
}, # replace values by needed ones to delete the config, data-submit-form attributs will parse and submit values
)["data"],
],
},
)["data"],
]
),
},
]
builder = [
{
"type": "card",
"display": ["main", 1],
"widgets": [
tabulator(
id="table-core-plugins",
columns=users_columns,
items=users_items,
filters=users_filters,
),
get_forms(),
],
},
]
with open("configs2.json", "w") as f:
f.write(json.dumps(builder))
output_base64_bytes = base64.b64encode(bytes(json.dumps(builder), "utf-8"))
output_base64_string = output_base64_bytes.decode("ascii")
with open("configs2.txt", "w") as f:
f.write(output_base64_string)

View 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/Bans.vue
* @description This component is lightweight builder containing only the necessary components to create the bans 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>

View file

@ -364,13 +364,15 @@ onUnmounted(() => {
@click="advancedForm.submit()"
/>
</div>
<div class="flex justify-center items-center" data-is="form-error">
<div
v-if="
(Object.keys(advancedForm.templateUIFormat).length && data.isRegErr) ||
(Object.keys(advancedForm.templateUIFormat).length && data.isReqErr)
"
class="flex justify-center items-center"
data-is="form-error"
>
<Text
v-if="
(Object.keys(advancedForm.templateUIFormat).length &&
data.isRegErr) ||
(Object.keys(advancedForm.templateUIFormat).length && data.isReqErr)
"
:text="
data.isReqErr
? $t('dashboard_advanced_required', {

View file

@ -258,9 +258,12 @@ onUnmounted(() => {
v-bind="buttonSave"
/>
</div>
<div class="flex justify-center items-center" data-is="form-error">
<div
v-if="data.isRegErr || data.isReqErr"
class="flex justify-center items-center"
data-is="form-error"
>
<Text
v-if="data.isRegErr || data.isReqErr"
:text="
data.isReqErr
? $t('dashboard_easy_required', {

View file

@ -47,63 +47,65 @@ const props = defineProps({
* @returns {Object} - props object
*/
function getDataByField(setting, fallbackInpType) {
console;
// Start by the base = setting share by all fields
const base = {
inpType: setting.inpType || fallbackInpType,
id: setting.id || "",
columns: setting.columns || { pc: "12", tablet: "12", mobile: "12" },
value: setting.value || "",
popovers: setting.popovers || [],
disabled: setting.disabled || false,
required: setting.required || false,
label: setting.label || null,
name: setting.name || null,
hideLabel: setting.hideLabel || false,
containerClass: setting.containerClass || "",
headerClass: setting.headerClass || "",
inpClass: setting.inpClass || "",
tabId: setting.tabId || contentIndex,
attrs: setting.attrs || {},
fieldSize: setting.fieldSize || "normal",
inpType: setting?.inpType || fallbackInpType,
id: setting?.id || "",
columns: setting?.columns || { pc: "12", tablet: "12", mobile: "12" },
value: setting?.value || "",
popovers: setting?.popovers || [],
disabled: setting?.disabled || false,
required: setting?.required || false,
label: setting?.label || null,
name: setting?.name || null,
hideLabel: setting?.hideLabel || false,
containerClass: setting?.containerClass || "",
headerClass: setting?.headerClass || "",
inpClass: setting?.inpClass || "",
tabId: setting?.tabId || contentIndex,
attrs: setting?.attrs || {},
fieldSize: setting?.fieldSize || "normal",
hideFeedbackError: setting?.hideFeedbackError || false,
};
if (
setting.inpType === "select" ||
setting?.inpType === "select" ||
(!setting?.inpType && fallbackInpType === "select")
) {
base["values"] = setting.values || [];
base["maxBtnChars"] = setting.maxBtnChars || 0;
base["requiredValues"] = setting.requiredValues || [];
base["onlyDown"] = setting.onlyDown || false;
base["overflowAttrEl"] = setting.overflowAttrEl || "";
base["values"] = setting?.values || [];
base["maxBtnChars"] = setting?.maxBtnChars || 0;
base["requiredValues"] = setting?.requiredValues || [];
base["onlyDown"] = setting?.onlyDown || false;
base["overflowAttrEl"] = setting?.overflowAttrEl || "";
}
if (
setting.inpType === "datepicker" ||
setting?.inpType === "datepicker" ||
(!setting?.inpType && fallbackInpType === "datepicker")
) {
base["minDate"] = setting.minDate || "";
base["maxDate"] = setting.maxDate || "";
base["isClipboard"] = setting.isClipboard || false;
base["minDate"] = setting?.minDate || "";
base["maxDate"] = setting?.maxDate || "";
base["isClipboard"] = setting?.isClipboard || false;
}
if (
setting.inpType === "input" ||
setting?.inpType === "input" ||
(!setting?.inpType && fallbackInpType === "input")
) {
base["type"] = setting.type || "text";
base["placeholder"] = setting.placeholder || "";
base["pattern"] = setting.pattern || "";
base["isClipboard"] = setting.isClipboard || false;
base["readonly"] = setting.readonly;
base["type"] = setting?.type || "text";
base["placeholder"] = setting?.placeholder || "";
base["pattern"] = setting?.pattern || "";
base["isClipboard"] = setting?.isClipboard || false;
base["readonly"] = setting?.readonly;
}
if (
setting.inpType === "editor" ||
setting?.inpType === "editor" ||
(!setting?.inpType && fallbackInpType === "editor")
) {
base["pattern"] = setting.pattern || "";
base["isClipboard"] = setting.isClipboard || false;
base["pattern"] = setting?.pattern || "";
base["isClipboard"] = setting?.isClipboard || false;
}
return base;

View file

@ -216,12 +216,12 @@ onMounted(() => {
v-bind="buttonSave"
/>
</div>
<div class="flex justify-center items-center" data-is="form-error">
<Text
v-if="!data.isValid"
:text="'dashboard_raw_invalid'"
:textClass="'form-setting-error'"
/>
<div
v-if="!data.isValid"
class="flex justify-center items-center"
data-is="form-error"
>
<Text :text="'dashboard_raw_invalid'" :textClass="'form-setting-error'" />
</div>
</Container>
</template>

View file

@ -101,16 +101,24 @@ onMounted(() => {
:containerClass="`form-regular-container`"
>
<div class="form-regular-wrap">
<div :class="['layout-settings', `max-w-screen-${props.maxWidthScreen}`]">
<div
:class="[
'layout-settings-regular',
`max-w-screen-${props.maxWidthScreen}`,
]"
>
<template v-for="(field, key) in props.fields">
<Fields :setting="field.setting" />
</template>
</div>
<ButtonGroup :buttons="props.buttons" />
<div class="flex justify-center items-center" data-is="form-error">
<div
v-if="data.isRegErr || data.isReqErr"
class="flex justify-center items-center"
data-is="form-error"
>
<Text
v-if="data.isRegErr || data.isReqErr"
:text="
data.isReqErr
? $t('dashboard_regular_required', {

View file

@ -45,8 +45,8 @@ import { useUUID } from "@utils/global.js";
* @param {String} [inpClass=""]
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && value && method
id: {
@ -121,6 +121,11 @@ const props = defineProps({
required: false,
default: "normal",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
tabId: {
type: [String, Number],
required: false,
@ -161,7 +166,7 @@ onMounted(() => {
<template>
<Container
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -176,42 +181,46 @@ onMounted(() => {
/>
<div class="checkbox-container">
<input
v-bind="props.attrs"
ref="checkboxEl"
:tabindex="props.tabId"
@keyup.enter="$emit('inp', updateValue())"
@click="$emit('inp', updateValue())"
:id="checkbox.id"
:name="props.name"
:disabled="props.disabled || false"
:checked="checkbox.value === 'yes' ? true : false"
:class="[
'checkbox',
checkbox.value === 'yes' ? 'check' : '',
checkbox.isValid ? 'valid' : 'invalid',
props.inpClass,
props.fieldSize,
]"
type="checkbox"
:value="checkbox.value"
:required="props.required || false"
/>
<div class="relative">
<input
v-bind="props.attrs"
ref="checkboxEl"
:tabindex="props.tabId"
@keyup.enter="$emit('inp', updateValue())"
@click="$emit('inp', updateValue())"
:id="checkbox.id"
:name="props.name"
:disabled="props.disabled || false"
:checked="checkbox.value === 'yes' ? true : false"
:class="[
'checkbox',
checkbox.value === 'yes' ? 'check' : '',
checkbox.isValid ? 'valid' : 'invalid',
props.inpClass,
props.fieldSize,
]"
type="checkbox"
:value="checkbox.value"
:required="props.required || false"
/>
<svg
role="img"
aria-hidden="true"
v-show="checkbox.value === 'yes'"
:class="['checkbox-svg', props.fieldSize]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
class="pointer-events-none"
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"
></path>
</svg>
</div>
<svg
role="img"
aria-hidden="true"
v-show="checkbox.value === 'yes'"
:class="['checkbox-svg', props.fieldSize]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
class="pointer-events-none"
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"
></path>
</svg>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'check'"
:isValid="checkbox.isValid"
:isValue="checkbox.isValid"

View file

@ -60,8 +60,8 @@ import { useUUID } from "@utils/global.js";
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String} [headerClass=""]
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && value && method
id: {
@ -69,6 +69,11 @@ const props = defineProps({
required: false,
default: "",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
columns: {
type: [Object, Boolean],
required: false,
@ -389,7 +394,7 @@ const emits = defineEmits(["inp"]);
<Container
data-field-container
:class="[select.isOpen ? 'z-[100]' : '']"
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -557,6 +562,7 @@ const emits = defineEmits(["inp"]);
</div>
</div>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'combobox'"
:isValid="select.isValid"
:isValue="true"

View file

@ -59,8 +59,8 @@ import "@assets/css/flatpickr.dark.min.css";
* @param {String} [containerClass=""]
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && type && disabled && required && value
id: {
@ -68,6 +68,11 @@ const props = defineProps({
required: false,
default: "",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
fieldSize: {
type: String,
required: false,
@ -150,6 +155,7 @@ const props = defineProps({
required: false,
default: "",
},
tabId: {
type: [String, Number],
required: false,
@ -735,7 +741,7 @@ onUnmounted(() => {
<Container
:class="[picker.isOpen ? 'z-[100]' : '']"
v-if="props.inpType === 'datepicker'"
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -794,6 +800,7 @@ onUnmounted(() => {
:copyClass="'datepicker-clip'"
/>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'picker'"
:isValid="date.isValid"
:isValue="!!date.value"

View file

@ -54,8 +54,8 @@ import "@assets/script/editor/theme-dawn.js";
* @param {String} [headerClass=""]
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && value && method
id: {
@ -63,6 +63,11 @@ const props = defineProps({
required: false,
default: "",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
columns: {
type: [Object, Boolean],
required: false,
@ -368,7 +373,7 @@ onUnmounted(() => {
<template>
<Container
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -410,6 +415,7 @@ onUnmounted(() => {
/>
</div>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'editor'"
:isValid="editor.isValid"
:isValue="!!editor.value"

View file

@ -60,8 +60,8 @@ import { useUUID } from "@utils/global.js";
* @param {String} [headerClass=""]
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && value && method
id: {
@ -69,6 +69,11 @@ const props = defineProps({
required: false,
default: "",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
fieldSize: {
type: String,
required: false,
@ -193,7 +198,7 @@ onMounted(() => {
<template>
<Container
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -297,6 +302,7 @@ onMounted(() => {
</button>
</div>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'input'"
:isValid="inp.isValid"
:isValue="!!inp.value"

View file

@ -55,8 +55,8 @@ import ErrorDropdown from "@components/Forms/Error/Dropdown.vue";
* @param {String} [headerClass=""]
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [showErrMsg=false] - Show additionnal required or invalid error message at the bottom of the input. Disable by default because help popover, label and outline color are enough for the user.
*/
const props = defineProps({
// id && value && method
id: {
@ -64,6 +64,11 @@ const props = defineProps({
required: false,
default: "",
},
showErrMsg: {
type: Boolean,
required: false,
default: false,
},
fieldSize: {
type: String,
required: false,
@ -197,7 +202,6 @@ const inp = reactive({
});
const inputEl = ref();
const selectWidth = ref("");
const selectDropdown = ref();
/**
@ -373,15 +377,6 @@ onBeforeMount(() => {
inp.id = useUUID(props.id);
});
onMounted(() => {
selectWidth.value = `${inputEl.value.clientWidth}px`;
window.addEventListener("resize", () => {
try {
selectWidth.value = `${inputEl.value.clientWidth}px`;
} catch (err) {}
});
});
const emits = defineEmits(["inp"]);
</script>
@ -389,7 +384,7 @@ const emits = defineEmits(["inp"]);
<Container
data-field-container
:class="[inp.isOpen ? 'z-[100]' : '']"
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -486,7 +481,6 @@ const emits = defineEmits(["inp"]);
:aria-hidden="inp.isOpen ? 'false' : 'true'"
:aria-expanded="inp.isOpen ? 'true' : 'false'"
ref="selectDropdown"
:style="{ width: selectWidth }"
:id="`${inp.id}-custom`"
:class="[inp.isOpen ? 'open' : 'close']"
class="list-dropdown-container"
@ -538,9 +532,11 @@ const emits = defineEmits(["inp"]);
<ErrorField
:errorClass="'input'"
v-if="
(!inp.isOpen && !inp.isMatching) ||
(!inp.isOpen && !inp.isEnterValid) ||
(!inp.isOpen && !inp.isValid)
props.showErrMsg
? (!inp.isOpen && !inp.isMatching) ||
(!inp.isOpen && !inp.isEnterValid) ||
(!inp.isOpen && !inp.isValid)
: false
"
:isValid="inp.isValid && !inp.isEnterMatching && inp.isEnterValid"
:isValue="props.required ? !!inp.value : true"

View file

@ -59,6 +59,7 @@ import { useUUID } from "@utils/global";
* @param {String} [headerClass=""]
* @param {String} [fieldSize="normal"] - Size between "normal" or "sm"
* @param {String|Number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
* @param {Boolean} [hideValidation=false] - If field should be validate and show error. Useful to disable it for filters.
*/
const props = defineProps({
@ -68,6 +69,11 @@ const props = defineProps({
required: false,
default: "",
},
hideValidation: {
type: Boolean,
required: false,
default: false,
},
fieldSize: {
type: String,
required: false,
@ -184,7 +190,6 @@ const select = reactive({
});
const selectBtn = ref();
const selectWidth = ref("");
const selectDropdown = ref();
/**
@ -345,15 +350,6 @@ onBeforeMount(() => {
select.id = useUUID(props.id);
});
onMounted(() => {
selectWidth.value = `${selectBtn.value.clientWidth}px`;
window.addEventListener("resize", () => {
try {
selectWidth.value = `${selectBtn.value.clientWidth}px`;
} catch (err) {}
});
});
const emits = defineEmits(["inp"]);
</script>
@ -361,7 +357,7 @@ const emits = defineEmits(["inp"]);
<Container
:class="[select.isOpen ? 'z-[100]' : '']"
data-field-container
:containerClass="`${props.containerClass}`"
:containerClass="`${props.containerClass} input-container`"
:columns="props.columns"
>
<Header
@ -446,7 +442,6 @@ const emits = defineEmits(["inp"]);
:aria-expanded="select.isOpen ? 'true' : 'false'"
ref="selectDropdown"
role="radiogroup"
:style="{ width: selectWidth }"
:id="`${select.id}-custom`"
:class="[select.isOpen ? 'open' : 'close']"
class="select-dropdown-container"
@ -482,6 +477,7 @@ const emits = defineEmits(["inp"]);
</button>
</div>
<ErrorField
v-if="props.showErrMsg"
:errorClass="'select'"
:isValid="select.isValid"
:isValue="true"

View file

@ -0,0 +1,58 @@
<script setup>
import { defineProps, reactive } from "vue";
/**
* @name Icons/Back.vue
* @description This component is a svg icon representing back.
* @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="box"
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="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z"
clip-rule="evenodd"
/>
</svg>
</template>

View file

@ -27,13 +27,13 @@ const props = defineProps({
color: {
type: String,
required: false,
default: "dark",
default: "sky",
},
disabled: { type: Boolean, required: false, default: false },
});
const icon = reactive({
color: props.color || "dark",
color: props.color || "sky",
});
</script>
<template>

View file

@ -38,6 +38,7 @@ import Pen from "@components/Icons/Pen.vue";
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";
/**
* @name Widget/Icons.vue
@ -110,6 +111,7 @@ onMounted(() => {
v-if="useEqualStr(props.iconName, 'exclamation')"
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" />
<Carton v-if="useEqualStr(props.iconName, 'carton')" v-bind="icon" />

View file

@ -293,7 +293,7 @@ onMounted(() => {
<div data-is="table" class="layout-table">
<Container
v-if="props.filters.length"
:containerClass="'layout-table-settings'"
:containerClass="'layout-settings-table'"
>
<template v-for="filter in props.filters">
<Fields

View file

@ -36,6 +36,7 @@
"dashboard_cache": "cache",
"dashboard_logs": "logs",
"dashboard_raw": "raw mode",
"dashboard_usermanagement": "user management",
"dashboard_feedback_toggle_sidebar": "Toggle feedback sidebar.",
"dashboard_feedback_close_sidebar": "Close feedback sidebar.",
"dashboard_feedback_alert_close": "Close feedback alert",
@ -171,6 +172,7 @@
"action_toggle": "toggle {name}",
"action_unban": "unban {name}",
"action_entry": "entry {name}",
"action_back": "back",
"home_version": "version",
"home_all_features_available": "all features are available",
"home_awaiting_compliance": "awaiting compliance",
@ -196,6 +198,7 @@
"instances_name": "name",
"instances_hostname": "hostname",
"instances_search": "search",
"instances_search_placeholder": "(host)name",
"instances_search_popover": "Search by hostname or name keyword.",
"instances_type": "type",
"instances_type_popover": "Select the instance type.",
@ -209,6 +212,10 @@
"instances_delete_subtitle": "Are you sure to delete the following instance ?",
"instances_create_title": "Create instance",
"instances_create_subtitle": "Notice that port and server name will be set by scheduler.",
"instances_hostname_placeholder": "tcp://docker:2375",
"instances_hostname_desc": "The adress of the server used for the instance.",
"instances_name_placeholder": "awesome-instance",
"instances_name_desc": "The name of the instance.",
"instances_tab_list": "Instances",
"instances_tab_add": "Add instance",
"instances_list_title": "Instances",
@ -253,6 +260,7 @@
"reports_subtitle": "List of reports catch by BunkerWeb.",
"reports_not_found": "No reports found",
"reports_search_misc": "Search misc",
"reports_search_misc_placeholder": "/admin",
"reports_search_misc_desc": "Misc will search within ip, url, user agent or data.",
"reports_select_reason": "Select reason",
"reports_select_reason_desc": "Reason is the plugin that triggered the report.",
@ -276,6 +284,7 @@
"bans_ban_end_date": "Unban date.",
"bans_search_ip": "Search ip",
"bans_search_ip_desc": "Search within ip address matching keyword.",
"bans_search_ip_placeholder": "127.0.0.1",
"bans_select_reason": "Select reason",
"bans_select_reason_desc": "Reason is the plugin that triggered the ban.",
"bans_select_remain": "Select remain",
@ -321,5 +330,46 @@
"logs_select_file_info": "Log files are retrieve using syslog under the hood.",
"logs_log_file": "Log files",
"logs_not_selected_or_not_found": "No log file selected or content not found",
"logs_file_content": "Log content"
"logs_file_content": "Log content",
"user_management_search": "Search user",
"user_management_search_placeholder": "john.doe{'@'}gmail.com",
"user_management_search_desc": "Search within user name or email",
"user_management_select_role": "Search role",
"user_management_select_role_desc": "Search within user role",
"user_management_select_is_totp": "Search TOTP",
"user_management_select_is_totp_desc": "Search within user TOTP status",
"user_management_delete_title": "Delete user",
"user_management_delete_subtitle": "Are you sure you want to delete this user ?",
"user_management_creation_date": "Creation date",
"user_management_last_login_date": "Last login date",
"user_management_last_update_date": "Last update date",
"user_management_create_title": "Create user",
"user_management_create_subtitle": "Add a member to your team.",
"user_management_edit_title": "Edit user",
"user_management_edit_subtitle": "Update user information.",
"user_management_form_username": "Username",
"user_management_form_username_placeholder": "john.doe",
"user_management_form_username_desc": "Username is unique and required. It can't be changed.",
"user_management_form_email": "Email",
"user_management_form_email_desc": "Email is optional but recommended for password recovery.",
"user_management_form_role": "Role",
"user_management_form_role_desc": "Role is required and define user permissions.",
"user_management_form_password": "Password",
"user_management_form_password_desc": "Define the password for your new user.",
"user_management_form_password_placeholder": "P{'@'}ssw0rd",
"user_management_form_edit_password": "Edit password ?",
"user_management_form_edit_password_desc": "Edit password is optional. Leave empty with confirm password to unchanged.",
"user_management_form_edit_password_placeholder": "empty to unchanged",
"user_management_form_password_confirm": "Confirm password",
"user_management_form_password_confirm_desc": "Ensure the user will get the right password.",
"user_management_form_password_confirm_placeholder": "P{'@'}ssw0rd",
"user_management_form_edit_password_confirm": "Edit password confirm ?",
"user_management_form_edit_password_confirm_desc": "Leave empty with confirm password to unchanged.",
"user_management_form_edit_password_confirm_placeholder": "empty to unchanged",
"user_management_tab_list": "Users",
"user_management_tab_add": "Add user",
"user_management_list_title": "Users",
"user_management_list_subtitle": "Manage your team members.",
"user_management_users_not_found": "No users found",
"user_management_missing_roles": "Impossible to retrieve roles"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -20,7 +20,7 @@
></div>
<div
class="hidden"
data-server-builder='W3sidHlwZSI6ICJjYXJkIiwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogInJlcG9ydHNfdGl0bGUifX0sIHsidHlwZSI6ICJTdWJ0aXRsZSIsICJkYXRhIjogeyJzdWJ0aXRsZSI6ICJyZXBvcnRzX3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGFidWxhdG9yIiwgImRhdGEiOiB7ImlkIjogInRhYmxlLWNvcmUtcGx1Z2lucyIsICJjb2x1bW5zIjogW3sidGl0bGUiOiAiRGF0ZSIsICJmaWVsZCI6ICJkYXRlIiwgImZvcm1hdHRlciI6ICJmaWVsZHMiLCAibWluV2lkdGgiOiAyNTB9LCB7InRpdGxlIjogIklQIiwgImZpZWxkIjogImlwIiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiQ291bnRyeSIsICJmaWVsZCI6ICJjb3VudHJ5IiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiU2VydmVyIG5hbWUiLCAiZmllbGQiOiAic2VydmVyX25hbWUiLCAiZm9ybWF0dGVyIjogInRleHQifSwgeyJ0aXRsZSI6ICJNZXRob2QiLCAiZmllbGQiOiAibWV0aG9kIiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiVVJMIiwgImZpZWxkIjogInVybCIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIkNvZGUiLCAiZmllbGQiOiAiY29kZSIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIlVzZXIgYWdlbnQiLCAiZmllbGQiOiAidXNlcl9hZ2VudCIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIlJlYXNvbiIsICJmaWVsZCI6ICJyZWFzb24iLCAiZm9ybWF0dGVyIjogInRleHQifSwgeyJ0aXRsZSI6ICJEYXRhIiwgImZpZWxkIjogImRhdGEiLCAiZm9ybWF0dGVyIjogInRleHQifV0sICJpdGVtcyI6IFt7ImRhdGUiOiB7InNldHRpbmciOiB7ImxhYmVsIjogInJlcG9ydHNfZGF0ZSIsICJuYW1lIjogImRhdGVwaWNrZXItZGF0ZS0wIiwgImlkIjogImRhdGVwaWNrZXItZGF0ZS0wIiwgInZhbHVlIjogMTcyMzQ5MTczOTk1NCwgImhpZGVMYWJlbCI6IHRydWUsICJkaXNhYmxlZCI6IHRydWUsICJpbnBUeXBlIjogImRhdGVwaWNrZXIifX0sICJzZXJ2ZXJfbmFtZSI6IHsidGV4dCI6ICJsb2NhbGhvc3QifSwgImlwIjogeyJ0ZXh0IjogIjEyNy4wLjAuMSJ9LCAiY291bnRyeSI6IHsidGV4dCI6ICJFTiJ9LCAibWV0aG9kIjogeyJ0ZXh0IjogIlBPU1QifSwgInVybCI6IHsidGV4dCI6ICIvYWRtaW4ifSwgImNvZGUiOiB7InRleHQiOiAiNDAwIn0sICJ1c2VyX2FnZW50IjogeyJ0ZXh0IjogIk1vemlsbGEvNS4wIn0sICJyZWFzb24iOiB7InRleHQiOiAiYW50aWJvdCJ9LCAiZGF0YSI6IHsidGV4dCI6ICJsb3JlIGlwc3VtIGFkIHZpdGFtIGFldGVybmFtIn19LCB7ImRhdGUiOiB7InNldHRpbmciOiB7ImxhYmVsIjogInJlcG9ydHNfZGF0ZSIsICJuYW1lIjogImRhdGVwaWNrZXItZGF0ZS0xIiwgImlkIjogImRhdGVwaWNrZXItZGF0ZS0xIiwgInZhbHVlIjogMTcyMzQ5MTczODAwMCwgImhpZGVMYWJlbCI6IHRydWUsICJkaXNhYmxlZCI6IHRydWUsICJpbnBUeXBlIjogImRhdGVwaWNrZXIifX0sICJzZXJ2ZXJfbmFtZSI6IHsidGV4dCI6ICJsb2NhbGhvc3QifSwgImlwIjogeyJ0ZXh0IjogIjEyNy4wLjAuMiJ9LCAiY291bnRyeSI6IHsidGV4dCI6ICJFTiJ9LCAibWV0aG9kIjogeyJ0ZXh0IjogIkdFVCJ9LCAidXJsIjogeyJ0ZXh0IjogIi9ldGM/In0sICJjb2RlIjogeyJ0ZXh0IjogIjMwMCJ9LCAidXNlcl9hZ2VudCI6IHsidGV4dCI6ICJNb3ppbGxhLzAuMSJ9LCAicmVhc29uIjogeyJ0ZXh0IjogInVua25vd24ifSwgImRhdGEiOiB7InRleHQiOiAiIn19XSwgImZpbHRlcnMiOiBbeyJ0eXBlIjogImxpa2UiLCAiZmllbGRzIjogWyJpcCIsICJ1cmwiLCAidXNlcl9hZ2VudCIsICJkYXRhIl0sICJzZXR0aW5nIjogeyJpZCI6ICJpbnB1dC1zZWFyY2gtbWlzYyIsICJuYW1lIjogImlucHV0LXNlYXJjaC1taXNjIiwgImxhYmVsIjogInJlcG9ydHNfc2VhcmNoX21pc2MiLCAidmFsdWUiOiAiIiwgImlucFR5cGUiOiAiaW5wdXQiLCAiY29sdW1ucyI6IHsicGMiOiAzLCAidGFibGV0IjogNCwgIm1vYmlsZSI6IDEyfSwgImZpZWxkU2l6ZSI6ICJzbSIsICJwb3BvdmVycyI6IFt7Imljb25OYW1lIjogImluZm8iLCAidGV4dCI6ICJyZXBvcnRzX3NlYXJjaF9taXNjX2Rlc2MifV19fSwgeyJ0eXBlIjogIj0iLCAiZmllbGRzIjogWyJyZWFzb24iXSwgInNldHRpbmciOiB7ImlkIjogInNlbGVjdC1yZWFzb24iLCAibmFtZSI6ICJzZWxlY3QtcmVhc29uIiwgImxhYmVsIjogInJlcG9ydHNfc2VsZWN0X3JlYXNvbiIsICJ2YWx1ZSI6ICJhbGwiLCAidmFsdWVzIjogWyJhbGwiLCAidW5rbm93biIsICJhbnRpYm90Il0sICJpbnBUeXBlIjogInNlbGVjdCIsICJvbmx5RG93biI6IHRydWUsICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9LCAiZmllbGRTaXplIjogInNtIiwgInBvcG92ZXJzIjogW3siaWNvbk5hbWUiOiAiaW5mbyIsICJ0ZXh0IjogInJlcG9ydHNfc2VsZWN0X3JlYXNvbl9kZXNjIn1dfX0sIHsidHlwZSI6ICI9IiwgImZpZWxkcyI6IFsibWV0aG9kIl0sICJzZXR0aW5nIjogeyJpZCI6ICJzZWxlY3QtbWV0aG9kIiwgIm5hbWUiOiAic2VsZWN0LW1ldGhvZCIsICJsYWJlbCI6ICJyZXBvcnRzX3NlbGVjdF9tZXRob2QiLCAidmFsdWUiOiAiYWxsIiwgInZhbHVlcyI6IFsiYWxsIiwgIkdFVCIsICJQT1NUIl0sICJpbnBUeXBlIjogInNlbGVjdCIsICJvbmx5RG93biI6IHRydWUsICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9LCAiZmllbGRTaXplIjogInNtIiwgInBvcG92ZXJzIjogW3siaWNvbk5hbWUiOiAiaW5mbyIsICJ0ZXh0IjogInJlcG9ydHNfc2VsZWN0X21ldGhvZF9kZXNjIn1dfX0sIHsidHlwZSI6ICI9IiwgImZpZWxkcyI6IFsiY29kZSJdLCAic2V0dGluZyI6IHsiaWQiOiAic2VsZWN0LWNvZGUiLCAibmFtZSI6ICJzZWxlY3QtY29kZSIsICJsYWJlbCI6ICJyZXBvcnRzX3NlbGVjdF9jb2RlIiwgInZhbHVlIjogImFsbCIsICJ2YWx1ZXMiOiBbImFsbCIsICIzMDAiLCAiNDAwIl0sICJpbnBUeXBlIjogInNlbGVjdCIsICJvbmx5RG93biI6IHRydWUsICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9LCAiZmllbGRTaXplIjogInNtIiwgInBvcG92ZXJzIjogW3siaWNvbk5hbWUiOiAiaW5mbyIsICJ0ZXh0IjogInJlcG9ydHNfc2VsZWN0X2NvZGVfZGVzYyJ9XX19XX19XX1d'
data-server-builder='W3sidHlwZSI6ICJjYXJkIiwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogInJlcG9ydHNfdGl0bGUifX0sIHsidHlwZSI6ICJTdWJ0aXRsZSIsICJkYXRhIjogeyJzdWJ0aXRsZSI6ICJyZXBvcnRzX3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGFidWxhdG9yIiwgImRhdGEiOiB7ImlkIjogInRhYmxlLWNvcmUtcGx1Z2lucyIsICJjb2x1bW5zIjogW3sidGl0bGUiOiAiRGF0ZSIsICJmaWVsZCI6ICJkYXRlIiwgImZvcm1hdHRlciI6ICJmaWVsZHMiLCAibWluV2lkdGgiOiAyNTB9LCB7InRpdGxlIjogIklQIiwgImZpZWxkIjogImlwIiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiQ291bnRyeSIsICJmaWVsZCI6ICJjb3VudHJ5IiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiU2VydmVyIG5hbWUiLCAiZmllbGQiOiAic2VydmVyX25hbWUiLCAiZm9ybWF0dGVyIjogInRleHQifSwgeyJ0aXRsZSI6ICJNZXRob2QiLCAiZmllbGQiOiAibWV0aG9kIiwgImZvcm1hdHRlciI6ICJ0ZXh0In0sIHsidGl0bGUiOiAiVVJMIiwgImZpZWxkIjogInVybCIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIkNvZGUiLCAiZmllbGQiOiAiY29kZSIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIlVzZXIgYWdlbnQiLCAiZmllbGQiOiAidXNlcl9hZ2VudCIsICJmb3JtYXR0ZXIiOiAidGV4dCJ9LCB7InRpdGxlIjogIlJlYXNvbiIsICJmaWVsZCI6ICJyZWFzb24iLCAiZm9ybWF0dGVyIjogInRleHQifSwgeyJ0aXRsZSI6ICJEYXRhIiwgImZpZWxkIjogImRhdGEiLCAiZm9ybWF0dGVyIjogInRleHQifV0sICJpdGVtcyI6IFt7ImRhdGUiOiB7InNldHRpbmciOiB7ImxhYmVsIjogInJlcG9ydHNfZGF0ZSIsICJuYW1lIjogImRhdGVwaWNrZXItZGF0ZS0wIiwgImlkIjogImRhdGVwaWNrZXItZGF0ZS0wIiwgInZhbHVlIjogMTcyMzQ5MTczOTk1NCwgImhpZGVMYWJlbCI6IHRydWUsICJkaXNhYmxlZCI6IHRydWUsICJpbnBUeXBlIjogImRhdGVwaWNrZXIifX0sICJzZXJ2ZXJfbmFtZSI6IHsidGV4dCI6ICJsb2NhbGhvc3QifSwgImlwIjogeyJ0ZXh0IjogIjEyNy4wLjAuMSJ9LCAiY291bnRyeSI6IHsidGV4dCI6ICJFTiJ9LCAibWV0aG9kIjogeyJ0ZXh0IjogIlBPU1QifSwgInVybCI6IHsidGV4dCI6ICIvYWRtaW4ifSwgImNvZGUiOiB7InRleHQiOiAiNDAwIn0sICJ1c2VyX2FnZW50IjogeyJ0ZXh0IjogIk1vemlsbGEvNS4wIn0sICJyZWFzb24iOiB7InRleHQiOiAiYW50aWJvdCJ9LCAiZGF0YSI6IHsidGV4dCI6ICJsb3JlIGlwc3VtIGFkIHZpdGFtIGFldGVybmFtIn19LCB7ImRhdGUiOiB7InNldHRpbmciOiB7ImxhYmVsIjogInJlcG9ydHNfZGF0ZSIsICJuYW1lIjogImRhdGVwaWNrZXItZGF0ZS0xIiwgImlkIjogImRhdGVwaWNrZXItZGF0ZS0xIiwgInZhbHVlIjogMTcyMzQ5MTczODAwMCwgImhpZGVMYWJlbCI6IHRydWUsICJkaXNhYmxlZCI6IHRydWUsICJpbnBUeXBlIjogImRhdGVwaWNrZXIifX0sICJzZXJ2ZXJfbmFtZSI6IHsidGV4dCI6ICJsb2NhbGhvc3QifSwgImlwIjogeyJ0ZXh0IjogIjEyNy4wLjAuMiJ9LCAiY291bnRyeSI6IHsidGV4dCI6ICJFTiJ9LCAibWV0aG9kIjogeyJ0ZXh0IjogIkdFVCJ9LCAidXJsIjogeyJ0ZXh0IjogIi9ldGM/In0sICJjb2RlIjogeyJ0ZXh0IjogIjMwMCJ9LCAidXNlcl9hZ2VudCI6IHsidGV4dCI6ICJNb3ppbGxhLzAuMSJ9LCAicmVhc29uIjogeyJ0ZXh0IjogInVua25vd24ifSwgImRhdGEiOiB7InRleHQiOiAiIn19XSwgImZpbHRlcnMiOiBbeyJ0eXBlIjogImxpa2UiLCAiZmllbGRzIjogWyJpcCIsICJ1cmwiLCAidXNlcl9hZ2VudCIsICJkYXRhIl0sICJzZXR0aW5nIjogeyJpZCI6ICJpbnB1dC1zZWFyY2gtbWlzYyIsICJuYW1lIjogImlucHV0LXNlYXJjaC1taXNjIiwgImxhYmVsIjogInJlcG9ydHNfc2VhcmNoX21pc2MiLCAicGxhY2Vob2xkZXIiOiAicmVwb3J0c19zZWFyY2hfbWlzY19wbGFjZWhvbGRlciIsICJ2YWx1ZSI6ICIiLCAiaW5wVHlwZSI6ICJpbnB1dCIsICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9LCAiZmllbGRTaXplIjogInNtIiwgInBvcG92ZXJzIjogW3siaWNvbk5hbWUiOiAiaW5mbyIsICJ0ZXh0IjogInJlcG9ydHNfc2VhcmNoX21pc2NfZGVzYyJ9XX19LCB7InR5cGUiOiAiPSIsICJmaWVsZHMiOiBbInJlYXNvbiJdLCAic2V0dGluZyI6IHsiaWQiOiAic2VsZWN0LXJlYXNvbiIsICJuYW1lIjogInNlbGVjdC1yZWFzb24iLCAibGFiZWwiOiAicmVwb3J0c19zZWxlY3RfcmVhc29uIiwgInZhbHVlIjogImFsbCIsICJ2YWx1ZXMiOiBbImFsbCIsICJ1bmtub3duIiwgImFudGlib3QiXSwgImlucFR5cGUiOiAic2VsZWN0IiwgIm9ubHlEb3duIjogdHJ1ZSwgImNvbHVtbnMiOiB7InBjIjogMywgInRhYmxldCI6IDQsICJtb2JpbGUiOiAxMn0sICJmaWVsZFNpemUiOiAic20iLCAicG9wb3ZlcnMiOiBbeyJpY29uTmFtZSI6ICJpbmZvIiwgInRleHQiOiAicmVwb3J0c19zZWxlY3RfcmVhc29uX2Rlc2MifV19fSwgeyJ0eXBlIjogIj0iLCAiZmllbGRzIjogWyJtZXRob2QiXSwgInNldHRpbmciOiB7ImlkIjogInNlbGVjdC1tZXRob2QiLCAibmFtZSI6ICJzZWxlY3QtbWV0aG9kIiwgImxhYmVsIjogInJlcG9ydHNfc2VsZWN0X21ldGhvZCIsICJ2YWx1ZSI6ICJhbGwiLCAidmFsdWVzIjogWyJhbGwiLCAiUE9TVCIsICJHRVQiXSwgImlucFR5cGUiOiAic2VsZWN0IiwgIm9ubHlEb3duIjogdHJ1ZSwgImNvbHVtbnMiOiB7InBjIjogMywgInRhYmxldCI6IDQsICJtb2JpbGUiOiAxMn0sICJmaWVsZFNpemUiOiAic20iLCAicG9wb3ZlcnMiOiBbeyJpY29uTmFtZSI6ICJpbmZvIiwgInRleHQiOiAicmVwb3J0c19zZWxlY3RfbWV0aG9kX2Rlc2MifV19fSwgeyJ0eXBlIjogIj0iLCAiZmllbGRzIjogWyJjb2RlIl0sICJzZXR0aW5nIjogeyJpZCI6ICJzZWxlY3QtY29kZSIsICJuYW1lIjogInNlbGVjdC1jb2RlIiwgImxhYmVsIjogInJlcG9ydHNfc2VsZWN0X2NvZGUiLCAidmFsdWUiOiAiYWxsIiwgInZhbHVlcyI6IFsiYWxsIiwgIjQwMCIsICIzMDAiXSwgImlucFR5cGUiOiAic2VsZWN0IiwgIm9ubHlEb3duIjogdHJ1ZSwgImNvbHVtbnMiOiB7InBjIjogMywgInRhYmxldCI6IDQsICJtb2JpbGUiOiAxMn0sICJmaWVsZFNpemUiOiAic20iLCAicG9wb3ZlcnMiOiBbeyJpY29uTmFtZSI6ICJpbmZvIiwgInRleHQiOiAicmVwb3J0c19zZWxlY3RfY29kZV9kZXNjIn1dfX1dfX1dfV0='
></div>
<div id="app"></div>
<script type="module" src="reports.js"></script>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,69 @@
<script setup>
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderUserManagement from "@components/Builder/UserManagement.vue";
import { useGlobal } from "@utils/global.js";
import { useDisplayStore } from "@store/global.js";
/**
* @name Page/UserManagement.vue
* @description This component is the Usermanagement 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 userManagement = 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)))
: {};
userManagement.builder = data;
});
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>
<DashboardLayout>
<BuilderUserManagement
v-if="userManagement.builder"
:builder="userManagement.builder"
/>
</DashboardLayout>
</template>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import { getI18n } from "@utils/lang.js";
import UserManagement from "./UserManagement.vue";
const pinia = createPinia();
createApp(UserManagement)
.use(pinia)
.use(getI18n(["dashboard", "action", "inp", "icons", "user_management"]))
.mount("#app");

View file

@ -48,7 +48,7 @@ export const useDisplayStore = defineStore("display", () => {
const display = ref({});
function setDisplay(groupName, componentName) {
display.value[groupName] = componentName;
display.value[groupName] = String(componentName);
}
function getDisplayByGroupName(groupName) {
@ -60,7 +60,7 @@ export const useDisplayStore = defineStore("display", () => {
}
function isCurrentDisplay(groupName, componentName) {
return display.value[groupName] === componentName;
return display.value[groupName] === String(componentName);
}
return {

View file

@ -1,4 +1,5 @@
import { v4 as uuidv4 } from "uuid";
import { useDisplayStore } from "@store/global.js";
/**
* @name utils/global.js
@ -16,6 +17,7 @@ import { v4 as uuidv4 } from "uuid";
function useGlobal() {
window.addEventListener("click", useDataLinkAttr);
window.addEventListener("click", useSubmitAttr);
window.addEventListener("click", useHandleDisplay);
}
/**
@ -259,6 +261,30 @@ function useDataLinkAttr(e) {
window.location.href = link;
}
/**
* @name useHandleDisplay
* @description Listen to click event and check if the clicked element has a data-display-index attribute.
* Case it has, we will update the display store with the data-display-index value.
* @param {Event} e - The event to attach the function logic
* @returns {Void}
*/
function useHandleDisplay(e) {
const displayStore = useDisplayStore();
if (
!e.target.hasAttribute("data-display-index") ||
!e.target.hasAttribute("data-display-group")
)
return;
try {
const index = e.target.getAttribute("data-display-index");
const group = e.target.getAttribute("data-display-group");
displayStore.setDisplay(group, index);
} catch (e) {
console.error(e);
console.error("Display index bad format");
}
}
export {
useGlobal,
useUUID,

View file

@ -172,6 +172,10 @@ body {
}
/* INPUT */
.input-container {
@apply relative h-fit;
}
.input-header-container {
@apply z-20;
}
@ -193,7 +197,7 @@ body {
}
.input-header-label {
@apply relative lowercase capitalize-first transition duration-300 ease-in-out font-bold dark:text-gray-300;
@apply [word-break:break-word] relative lowercase capitalize-first transition duration-300 ease-in-out font-bold dark:text-gray-300;
}
.input-header-label.normal {
@ -213,35 +217,11 @@ body {
}
.input-error-msg {
@apply absolute text-red-500 text-[0.75rem] font-semibold mb-0 mt-0.5;
}
.input.input-error-msg {
@apply -bottom-5;
}
.picker.input-error-msg {
@apply -bottom-5;
}
.select.input-error-msg {
@apply -bottom-5;
}
.combobox.input-error-msg {
@apply -bottom-5;
}
.check.input-error-msg {
@apply -bottom-11;
}
.editor.input-error-msg {
@apply -bottom-3;
@apply relative text-red-500 text-[0.75rem] font-semibold m-0;
}
.valid.input-error-msg {
@apply pointer-events-none text-green-500 -z-10 opacity-0;
@apply pointer-events-none text-green-500 -z-10 hidden;
}
.input-list-add {
@ -253,7 +233,7 @@ body {
}
.checkbox-container {
@apply relative z-10 mt-1;
@apply flex flex-col z-10;
}
.checkbox {
@ -261,7 +241,7 @@ body {
relative dark:border-slate-600 dark:bg-slate-700 z-10 w-5 h-5
text-base rounded-1.4 duration-150 float-left
appearance-none border border-gray-300 bg-white bg-contain bg-center
mt-1 bg-no-repeat align-top transition-all disabled:bg-gray-400
bg-no-repeat align-top transition-all disabled:bg-gray-400
disabled:border-gray-400/0 dark:disabled:bg-gray-800
disabled:cursor-not-allowed
dark:disabled:border-gray-800/0 disabled:text-gray-700
@ -284,12 +264,9 @@ body {
@apply z-10 pointer-events-none absolute fill-gray-700 dark:fill-gray-100 left-0 top-0;
}
.checkbox-svg.normal {
@apply translate-x-[3px] translate-y-[7px] h-3.5 w-3.5;
}
.checkbox-svg.normal,
.checkbox-svg.sm {
@apply translate-x-[3px] translate-y-[7px] h-3 w-3;
@apply translate-x-[3px] translate-y-[3px] h-3.5 w-3.5;
}
.select-btn {
@ -314,11 +291,11 @@ body {
}
.select-dropdown-container {
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-fit mt-2;
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-full mt-2;
}
.list-dropdown-container {
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-fit mt-2;
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-full mt-2;
}
.select-dropdown-btn {
@ -590,10 +567,6 @@ body {
@apply py-4 col-span-12 grid grid-cols-12 w-full relative;
}
.layout-table-settings {
@apply mb-4 gap-y-4 sm:gap-x-4 md:gap-x-8 md:gap-y-8 col-span-12 grid grid-cols-12 w-full relative;
}
.layout-card {
@apply px-6 py-4 grid grid-cols-12 w-full overflow-visible relative transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out;
}
@ -627,7 +600,15 @@ body {
}
.layout-settings {
@apply py-4 gap-y-4 sm:gap-x-4 md:gap-x-8 md:gap-y-8 col-span-12 grid grid-cols-12 w-full relative;
@apply py-4 gap-y-4 md:gap-y-6 sm:gap-x-4 md:gap-x-6 col-span-12 grid grid-cols-12 w-full relative;
}
.layout-settings-regular {
@apply py-4 gap-y-4 sm:gap-x-4 md:gap-x-6 col-span-12 grid grid-cols-12 w-full relative;
}
.layout-settings-table {
@apply gap-y-2 sm:gap-x-4 md:gap-x-6 mb-3 col-span-12 grid grid-cols-12 w-full relative items-center;
}
.layout-settings-multiple {
@ -1261,7 +1242,7 @@ body {
}
.button-group-table {
@apply mb-4 col-span-12 flex justify-start items-center mx-0;
@apply mb-3 col-span-12 flex justify-start items-center mx-0;
}
.button-group-table-content {
@ -2028,6 +2009,10 @@ body {
@apply bg-white text-red-500 border border-red-500 hover:border-red-500/80 focus:border-red-500/80 hover:text-red-500/80 focus:text-red-500/80;
}
.btn.back:not([disabled]) {
@apply bg-white text-sky-500 border border-sky-500 hover:border-sky-500/80 focus:border-sky-500/80 hover:text-sky-500/80 focus:text-sky-500/80;
}
.btn.delete:not([disabled]),
.btn.error:not([disabled]),
.btn.red:not([disabled]) {

File diff suppressed because one or more lines are too long