mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
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:
parent
98bc4f1124
commit
e218f8e621
38 changed files with 1828 additions and 1107 deletions
|
|
@ -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",
|
||||
)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
460
src/ui/client/builder/pages/user_management.py
Normal file
460
src/ui/client/builder/pages/user_management.py
Normal 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
50
src/ui/client/builder/test_user_management.py
Normal file
50
src/ui/client/builder/test_user_management.py
Normal 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")
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
58
src/ui/client/dashboard/components/Icons/Back.vue
Normal file
58
src/ui/client/dashboard/components/Icons/Back.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
28
src/ui/client/dashboard/pages/usermanagement/index.html
Normal file
28
src/ui/client/dashboard/pages/usermanagement/index.html
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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");
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue