cache page done + build all base pages

This commit is contained in:
Jordan Blasenhauer 2024-08-19 18:03:13 +02:00
parent 955f2a4cea
commit 8ce44bc0fc
16 changed files with 714 additions and 130 deletions

View file

@ -65,6 +65,8 @@ def create_dir(directory: Path):
def create_base_dirs():
"""Create the base directories we will need to build front end and add them to flask app"""
create_dir(opt_dir_dashboard)
create_dir(ui_dir_static)
create_dir(ui_dir_templates)
def move_template(folder: Path, target_folder: Path):
@ -168,7 +170,8 @@ def build():
"""All steps to build the front end and set it to the flask app"""
reset()
create_base_dirs()
add_legacy()
# add_legacy()
# Only install packages if not already installed
if not current_directory.joinpath("node_modules").exists():
if run_command(["/usr/bin/npm", "install"], current_directory):

View file

@ -0,0 +1,32 @@
from .utils.widgets import file_manager_widget, title_widget, subtitle_widget, unmatch_widget
from typing import Optional
def fallback_message(msg: str, display: Optional[list] = None) -> dict:
return {
"type": "void",
"display": display if display else [],
"widgets": [
unmatch_widget(text=msg),
],
}
def cache_builder(file_manager: Optional[dict] = None) -> str:
if file_manager is None or (isinstance(file_manager, dict) and len(file_manager) == 0):
return [fallback_message("cache_not_found")]
return [
{
"type": "card",
"widgets": [
title_widget("cache_title"), # keep it (a18n)
subtitle_widget("cache_subtitle"), # keep it (a18n)
file_manager_widget(
data=file_manager,
),
],
},
]

View file

@ -875,6 +875,47 @@ def fields_widget(
return { "type" : "Fields", "data" : data }
def file_manager_widget(
data: dict,
baseFolder: str = "base",
columns: dict = {"pc":"12","tablet":"12","mobile":"12"},
containerClass: str = ""
):
"""
File manager component. Useful with cache page.
PARAMETERS
- `data` **Object** Can be a translation key or by default raw text.
- `baseFolder` **String** The base folder to display by default (optional, default `"base"`)
- `columns` **Object** Work with grid system { pc: 12, tablet: 12, mobile: 12} (optional, default `{"pc":"12","tablet":"12","mobile":"12"}`)
- `containerClass` **String** Additional class (optional, default `""`)
EXAMPLE
{
title: "Total Users",
type: "card",
titleClass: "text-lg",
color : "info",
tag: "h2"
}
"""
data = {
"data" : data,
}
# List of params that will be add only if not default value
list_params = [("baseFolder", baseFolder, "base"),("columns", columns, {"pc":"12","tablet":"12","mobile":"12"}),("containerClass", containerClass, "")]
for param in list_params:
add_key_value(data, param[0], param[1], param[2])
return { "type" : "Filemanager", "data" : data }
def filter_widget(
filters: Optional[list] = None,
data: Union[dict, list] = {},

View file

@ -0,0 +1,21 @@
from utils import save_builder
from pages.cache import cache_builder
# parents is parent folders
# children can be either files or folders
file_manager = {
# always need a root, this is the first folder display
"base": {"parents": [], "children": ["certificates", "configs", "test"], "type": "folder"},
"certificates": {"parents": ["base"], "children": ["cert-1"], "type": "folder"},
"cert-1": {"parents": ["base", "certificates"], "children": [], "type": "file", "downloadEndpoint": "/download/cert-1"},
"configs": {"parents": ["base"], "children": ["config-1"], "type": "folder"},
"config-1": {"parents": ["base", "configs"], "children": [], "type": "file", "downloadEndpoint": "/download/cert-1"},
"test": {"parents": ["base"], "children": ["test-1"], "type": "folder"},
"test-1": {"parents": ["base", "test"], "children": ["test-2"], "type": "folder", "downloadEndpoint": "/download/cert-1"},
"test-2": {"parents": ["base", "test", "test-1"], "children": [], "type": "file", "downloadEndpoint": "/download/cert-1"},
}
builder = cache_builder(file_manager=file_manager)
save_builder(page_name="cache", output=builder, script_name="cache")

View file

@ -0,0 +1,70 @@
<script setup>
// Containers
import Grid from "@components/Widget/Grid.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import Button from "@components/Widget/Button.vue";
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
import Title from "@components/Widget/Title.vue";
import Subtitle from "@components/Widget/Subtitle.vue";
import FileManager from "@components/Widget/FileManager.vue";
import Unmatch from "@components/Message/Unmatch.vue";
import { useEqualStr } from "@utils/global.js";
/**
* @name Builder/Cache.vue
* @description This component is lightweight builder containing only the necessary components to create the cache page.
* @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"
/>
<Unmatch
v-if="useEqualStr(widget.type, 'Unmatch')"
v-bind="widget.data"
/>
<FileManager
v-if="useEqualStr(widget.type, 'FileManager')"
v-bind="widget.data"
/>
<Button
v-if="useEqualStr(widget.type, 'Button')"
v-bind="widget.data"
/>
<ButtonGroup
v-if="useEqualStr(widget.type, 'ButtonGroup')"
v-bind="widget.data"
/>
</template>
</Grid>
</GridLayout>
</template>

View file

@ -0,0 +1,56 @@
<script setup>
import { defineProps, reactive } from "vue";
/**
* @name Icons/Folder.vue
* @description This component is a svg icon representing folder.
* @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: "dark",
},
disabled: { type: Boolean, required: false, default: false },
});
const icon = reactive({
color: props.color || "dark",
});
</script>
<template>
<svg
:data-color="icon.color"
:data-value="props.value"
:aria-disabled="props.disabled ? 'true' : 'false'"
data-svg="back"
role="img"
aria-hidden="true"
:class="[props.iconClass, icon.color, 'fill']"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z"
/>
</svg>
</template>

View file

@ -0,0 +1,126 @@
<script setup>
import Icons from "@components/Widget/Icons.vue";
import Container from "@components/Widget/Container.vue";
import Text from "@components/Widget/Text.vue";
import { reactive } from "vue";
/**
* @name Widget/FileManager.vue
* @description File manager component. Useful with cache page.
* @example
* {
* title: "Total Users",
* type: "card",
* titleClass: "text-lg",
* color : "info",
* tag: "h2"
* }
* @param {Object} data - Can be a translation key or by default raw text.
* @param {String} [baseFolder="base"] - The base folder to display by default
* @param {Object} [columns={"pc": "12", "tablet": "12", "mobile": "12"}] - Work with grid system { pc: 12, tablet: 12, mobile: 12}
* @param {String} [containerClass=""] - Additional class
*/
const props = defineProps({
data: {
type: Object,
required: true,
},
baseFolder: {
type: String,
required: false,
default: "base",
},
columns: {
type: Object,
required: false,
default: { pc: "12", tablet: "12", mobile: "12" },
},
containerClass: {
type: String,
required: false,
default: "",
},
});
const manager = reactive({
currFolder: "base", // by default, base
});
/**
* @name changeFolder
* @description Change current folder to the selected one if it is a folder.
* @param {String} name - The name of the folder or file
* @param {String} type - The type of element clicked, can be a folder or a file.
* @returns {void}
*/
function changeFolder(name, type) {
if (type !== "folder") return;
manager.currFolder = name;
}
/**
* @name downloadCache
* @description Download the cache file.
* @param {String} fileName - The cache filename to download
* @returns {void}
*/
function downloadCache(fileName) {
const url = `${location.href}${fileName}`;
const a = document.createElement("a");
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
</script>
<template>
<Container
data-is="file-manager"
:containerClass="`${props.containerClass}layout-file-manager`"
:columns="props.columns"
>
<div class="file-manager-breadcrumb">
<span class="file-manager-breadcrumb-base">/</span>
<button
class="file-manager-breadcrumb-btn"
@click="changeFolder(parent, 'folder')"
v-for="parent in props.data[manager.currFolder].parents"
>
{{ parent }}
</button>
</div>
<div class="file-manager-content">
<template v-for="child in props.data[manager.currFolder].children">
<button
@click="changeFolder(child, props.data[child].type)"
:class="[
props.data[child].type === 'file'
? 'cursor-default'
: 'cursor-pointer',
'file-manager-btn',
]"
>
<div class="flex justify-center items-end">
<Icons
:iconName="
props.data[child].type === 'file' ? 'document' : 'folder'
"
color="dark-darker"
/>
<button
class="hover:brightness-90"
v-if="props.data[child]?.downloadEndpoint"
@click="downloadCache(props.data[child]?.downloadEndpoint)"
>
<Icons iconName="download" color="info" />
</button>
</div>
<Text :text="child" />
</button>
</template>
</div>
</Container>
</template>

View file

@ -42,6 +42,7 @@ import Back from "@components/Icons/Back.vue";
import Refresh from "@components/Icons/Refresh.vue";
import Download from "@components/Icons/Download.vue";
import Clone from "@components/Icons/Clone.vue";
import Folder from "@components/Icons/Folder.vue";
/**
* @name Widget/Icons.vue
@ -114,6 +115,7 @@ onMounted(() => {
v-if="useEqualStr(props.iconName, 'exclamation')"
v-bind="icon"
/>
<Folder v-if="useEqualStr(props.iconName, 'folder')" v-bind="icon" />
<Clone v-if="useEqualStr(props.iconName, 'clone')" v-bind="icon" />
<Download v-if="useEqualStr(props.iconName, 'download')" v-bind="icon" />
<Refresh v-if="useEqualStr(props.iconName, 'refresh')" v-bind="icon" />

View file

@ -103,6 +103,11 @@ const props = defineProps({
const modalEl = ref();
/**
* @name useCloseModal
* @description Emits the close event to close the modal.
* @returns {void}
*/
function useCloseModal() {
emits("close");
}
@ -128,11 +133,23 @@ function useFocusModal() {
}, 1);
}
/**
* @name modalKeyboardEvents
* @description Handle the keyboard events for the modal.
* @param {Event} e - The event object.
* @returns {void}
*/
function modalKeyboardEvents(e) {
if (e.key === "Escape") useCloseModal();
if (e.key === "Tab" || e.key === "Shift-Tab") useFocusModal();
}
/**
* @name modalClickEvents
* @description Handle the click events for the modal.
* @param {Event} e - The event object.
* @returns {void}
*/
function modalClickEvents(e) {
if (
(e.target.closest("[data-modal]") !== modalEl.value && modalEl.value) ||
@ -142,11 +159,21 @@ function modalClickEvents(e) {
if (e.target.hasAttribute("data-close-modal")) useCloseModal();
}
/**
* @name setEvents
* @description Set the events for the modal when he is mounted.
* @returns {void}
*/
function setEvents() {
window.addEventListener("keydown", modalKeyboardEvents, true);
window.addEventListener("click", modalClickEvents);
}
/**
* @name unsetEvents
* @description Unset the events for the modal when he is unmounted.
* @returns {void}
*/
function unsetEvents() {
window.removeEventListener("keydown", modalKeyboardEvents, true);
window.removeEventListener("click", modalClickEvents);

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",
@ -102,6 +103,8 @@
"dashboard_no_match": "No match found",
"dashboard_no_match_filter": "No match found with filter",
"dashboard_something_wrong": "Something is wrong",
"dashboard_draft": "Select draft",
"dashboard_draft_desc": "Draft will not apply the configuration. Else the service will be online and apply the configuration.",
"inp_input_valid": "input valid",
"inp_input_error_no_match": "No match found",
"inp_input_error_required": "input is required",
@ -143,6 +146,8 @@
"icons_linkedin_desc": "Linkedin icon representing a link to a Linkedin profile.",
"icons_twitter_desc": "Twitter icon representing a link to a Twitter account.",
"action_switch": "switch {name}",
"action_manage": "manage {name}",
"action_clone": "clone {name}",
"action_send": "send {name}",
"action_start": "start {name}",
"action_disable": "disable {name}",
@ -164,9 +169,15 @@
"action_restart": "restart {name}",
"action_upload": "upload {name}",
"action_delete_all": "delete all {name}",
"action_select_all": "select all {name}",
"action_unselect_all": "unselect all {name}",
"action_previous": "previous",
"action_next": "next",
"action_toggle": "toggle {name}",
"action_unban": "unban {name}",
"action_entry": "entry {name}",
"action_update": "update {name}",
"action_back": "back",
"home_version": "version",
"home_all_features_available": "all features are available",
"home_awaiting_compliance": "awaiting compliance",
@ -191,16 +202,38 @@
"home_errors_found": "errors found",
"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_status": "status",
"instances_active": "active",
"instances_inactive": "inactive",
"instances_creation_date": "creation date",
"instances_last_seen": "last seen",
"instances_type_popover": "Select the instance type.",
"instances_method": "method",
"instances_method_popover": "Select the instance method.",
"instances_health": "health",
"instances_health_popover": "Select the instance health. Health is based on the last seen date.",
"instances_reload_title": "Reload instance",
"instances_reload_subtitle": "Are you sure to reload the following instance ?",
"instances_ping_title": "Ping instance",
"instances_ping_subtitle": "Ping will check instance health of the following instance.",
"instances_delete_title": "Delete instance",
"instances_delete_subtitle": "Are you sure to delete the following instance ?",
"instances_create_title": "Create instance",
"instances_create_subtitle": "Add a new instance to your list.",
"instances_hostname_placeholder": "http://random",
"instances_hostname_desc": "The adress of the server used for the instance.",
"instances_hostname_warning_desc": "Port and server name will be set by scheduler.",
"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",
"instances_list_subtitle": "List of instances related to BunkerWeb.",
"instances_not_found": "No instances found",
"global_config_title": "Global configuration",
"global_config_subtitle": "Manage your global settings.",
"jobs_title": "Jobs list",
"jobs_download_cache_file": "download cache files",
"jobs_no_cache_file": "no cache files",
"jobs_search": "search jobs",
"jobs_search_desc": "Search within job name, plugin id or last run.",
"jobs_interval": "Interval",
@ -212,6 +245,7 @@
"jobs_table_title": "Jobs list with plugin id, name, last run, interval and success state.",
"jobs_table_name": "Name",
"jobs_table_plugin_id": "Plugin id",
"jobs_history_title": "Job history",
"jobs_table_interval": "Interval",
"jobs_table_reload": "Reload",
"jobs_table_history": "History",
@ -221,57 +255,54 @@
"jobs_table_cache_downloadable": "Cache (downloadable)",
"jobs_history_subtitle": "Job history details.",
"jobs_history_table_title": "Job history list with start run date, end run date and success state.",
"plugins_pro_plugin_desc": "Pro plugin",
"plugins_core_plugin_desc": "Core plugin",
"plugins_external_plugin_desc": "External plugin",
"plugins_redirect_page_desc": "Redirect to plugin page",
"plugins_upload_title": "Upload plugin",
"plugins_upload_subtitle": "Extend BunkerWeb features.",
"plugins_list_tab": "Plugins",
"plugins_upload_tab": "Upload",
"plugins_search": "Search plugin",
"plugins_search_desc": "Search the plugin by his name",
"plugins_type": "Plugin type",
"plugins_type_desc": "Only show plugins of the chosen type",
"plugins_delete_desc": "Delete plugin",
"plugins_modal_delete_title": "Delete plugin",
"plugins_modal_delete_confirm": "Are you sure you want to delete the plugin below ?",
"reports_not_found": "No reports found",
"reports_search": "Search reports",
"reports_search_desc": "Search within report date, ip, url, user agent or data.",
"reports_country": "Country",
"reports_country_desc": "Country are alpha-2 country code based.",
"reports_method": "Method",
"reports_method_desc": "Methods are HTTP methods.",
"reports_status": "Status",
"reports_status_desc": "Status are HTTP status codes.",
"reports_reason": "Reason",
"reports_reason_desc": "Reason is the plugin name that triggered the report.",
"plugins_delete_title": "Delete plugin",
"plugins_delete_subtitle": "Are you sure you want to delete the plugin below ?",
"plugins_not_found": "No plugins found",
"plugins_list_title": "Plugins",
"plugins_list_subtitle": "Get details and manage your plugins.",
"reports_title": "Reports",
"reports_table_title": "Reports list with date, ip, country, method, url, status code, user agent, reason and data.",
"reports_table_date": "Date",
"reports_table_ip": "IP",
"reports_table_country": "Country",
"reports_table_method": "Method",
"reports_table_url": "URL",
"reports_table_status_code": "Status code",
"reports_table_cache_user_agent": "User agent",
"reports_table_reason": "Reason",
"reports_table_data": "Data",
"reports_total": "Total reports",
"reports_top_status": "Top status code",
"reports_top_reason": "Top reason",
"bans_search": "Search bans",
"bans_search_desc": "Search within ban ip, ban start / end date",
"bans_reason": "Reason",
"bans_reason_desc": "Reason is the method that triggered the ban.",
"bans_terms": "Term",
"bans_terms_desc": "Order of magnitude before unban.",
"bans_title": "Bans",
"bans_table_title": "Bans list with ip, start date, end date, reason and terms.",
"bans_table_ip": "IP",
"bans_table_ban_start": "Ban start",
"bans_table_ban_end": "Ban end",
"bans_table_reason": "Reason",
"bans_table_remain": "Remain",
"bans_table_term": "Term",
"bans_table_select": "Select",
"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.",
"reports_select_method": "Select method",
"reports_select_method_desc": "Method is the http method of the request.",
"reports_select_code": "Select code",
"reports_select_code_desc": "Code is the http status code returned by the request.",
"bans_not_found": "No bans found",
"bans_list_title": "Bans",
"bans_list_subtitle": "List of bans catch by BunkerWeb.",
"bans_unban_title": "Unban",
"bans_unban_subtitle": "Are you sure to unban selected IP(s) ?",
"bans_add_ban_ip": "Ip address to ban",
"bans_add_end_date": "Define unban date",
"bans_tab_list": "Bans",
"bans_add_title": "Add ban",
"bans_add_subtitle": "Manually add a ban.",
"bans_tab_add": "Add ban",
"bans_ban_check": "select ban",
"bans_ban_start_date": "Ban date.",
"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",
"bans_select_remain_desc": "Scale of remaining time before unban.",
"services_select_methods": "Select methods",
"services_select_draft": "Select draft",
"services_new": "new service",
"services_title": "Services",
"services_table_name": "Name",
@ -306,11 +337,143 @@
"services_mode_subtitle": "Manage your service settings.",
"services_manage_subtitle": "Manage your service settings.",
"services_no_easy_mode": "No easy mode for this template",
"services_clone_title": "Clone service",
"services_clone_subtitle": "Choose a mode to clone service",
"services_switch_mode": "Switch mode",
"services_switch_mode_title": "Switch mode",
"services_switch_mode_subtitle": "Choose a mode to switch",
"logs_title": "Logs",
"logs_not_found": "No logs found",
"logs_no_files_found": "No log files found",
"logs_select_file": "Select a log file",
"logs_select_file": "Select a file",
"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",
"configs_search_name": "Search name",
"configs_search_name_desc": "Search within config name",
"configs_search_name_placeholder": "redirect-conf",
"configs_select_global": "Select global",
"configs_select_global_desc": "Global config apply to all services, else it is apply to define services.",
"configs_select_type": "Select type",
"configs_select_type_desc": "The type of the config will determine the config's context execution.",
"configs_delete_title": "Delete config",
"configs_delete_subtitle": "Are you sure you want to delete the config below ?",
"configs_search_service": "Search service",
"configs_search_service_desc": "Search within services that are applying the config",
"configs_search_service_placeholder": "service-name",
"configs_show_services": "Show services applying to this config",
"configs_services_title": "Services",
"configs_create_title": "Create config",
"configs_create_subtitle": "Add a new config to your list.",
"configs_edit_title": "Edit config",
"configs_edit_subtitle": "Update config value.",
"configs_filename": "Config name",
"configs_filename_placeholder": "my-conf",
"configs_filename_warning_desc": "Config name matching another config name with the same type will not be created. Edit the existing one instead.",
"configs_filename_desc": "The name of the config.",
"configs_type": "Config type",
"configs_type_desc": "The type of the config will determine the config's context execution.",
"configs_services": "Services",
"configs_services_desc": "The services that are applying the config.",
"configs_services_warning_desc": "Case global value is check, will apply to every services. Case global uncheck and no services check, form will be ignored.",
"configs_value": "Configuration value",
"configs_value_desc": "The config script to apply.",
"configs_value_warning_desc": "Must be a valid config script. Case it is not, the config will not be saved/updated.",
"configs_tab_list": "Configs",
"configs_list_title": "Configs",
"configs_list_subtitle": "Current list of configs.",
"configs_tab_add": "Add config",
"configs_missing_types": "Impossible to retrieve config types",
"configs_missing_services": "Impossible to retrieve services",
"configs_not_found": "No configs found",
"profile_info_not_found": "Impossible to retrieve profile information",
"profile_info_title": "Information",
"profile_info_subtitle": "Details about your account.",
"profile_account_title": "Account",
"profile_account_subtitle": "Manage some settings.",
"profile_email": "Email",
"profile_email_placeholder": "john.doe{'@'}gmail.com",
"profile_email_desc": "Email is optional but recommended for password recovery or others features.",
"profile_new_password": "New password",
"profile_new_password_placeholder": "P{'@'}ssw0rd",
"profile_new_password_desc": "Optional. Leave empty with confirm password to unchanged.",
"profile_new_password_confirm": "New password confirm",
"profile_new_password_confirm_placeholder": "P{'@'}ssw0rd",
"profile_new_password_confirm_desc": "Optional. Leave empty with new password to unchanged.",
"profile_current_password": "Current password",
"profile_current_password_placeholder": "P{'@'}ssw0rd",
"profile_current_password_desc": "This ensure you are the owner of the account.",
"profile_recovery_codes_refresh_but_not_found": "Refresh done but no recovery codes found",
"profile_refresh_recovery_codes": "Refresh recovery codes",
"profile_totp_title": "TOTP",
"profile_totp_subtitle": "Two-factor authentication.",
"profile_totp_enable_state": "TOTP is currently enabled.",
"profile_totp_disable_form_title": "Disable TOTP",
"profile_totp_code": "TOTP code",
"profile_totp_code_placeholder": "123456",
"profile_totp_code_desc": "TOTP code is given by your authenticator app.",
"profile_totp_disable_state": "TOTP is currently disabled.",
"profile_totp_qr_code": "QR code to scan with your authenticator app.",
"profile_totp_secret": "Secret key",
"profile_totp_secret_placeholder": "secret",
"profile_totp_secret_desc": "Another way to add TOTP to your authenticator app.",
"profile_tab_profile": "Profile",
"profile_tab_account": "Account",
"profile_tab_totp": "TOTP",
"profile_user_not_found": "Impossible to retrieve user information",
"profile_totp_data_missing": "Impossible to retrieve TOTP data",
"profile_username": "Username",
"profile_created_method": "Created method",
"profile_role": "Role",
"profile_permissions": "Permissions",
"profile_creation_date": "Creation date",
"profile_last_update": "Last update",
"profile_totp_enable_form_title": "Enable TOTP",
"profile_account_tab_email": "Email",
"profile_account_tab_password": "Password",
"profile_account_form_email_title": "Update email",
"profile_account_form_password_title": "Update password",
"profile_no_recovery_codes": "No recovery codes remaining, please refresh them."
}

View file

@ -0,0 +1,37 @@
<script setup>
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderCache from "@components/Builder/Cache.vue";
import { useGlobal } from "@utils/global.js";
/**
* @name Page/Cache.vue
* @description This component is the cache page.
This page displays global information about cache, and allow to download a cache file.
*/
const cache = 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)))
: {};
cache.builder = data;
});
onMounted(() => {
useGlobal();
});
</script>
<template>
<DashboardLayout>
<BuilderCache v-if="cache.builder" :builder="cache.builder" />
</DashboardLayout>
</template>

View file

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

View file

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/img/favicon.ico" />
<link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Cache</title>
</head>
<body>
<div
class="hidden"
data-server-global='{"username" : "admin", "plugins_page": [{"id" : "antibot", "name": "Antibot"}, {"id": "backup", "name" : "backup"} ]}'
></div>
<div
class="hidden"
data-server-flash='[{"type" : "success", "title" : "title", "message" : "Success feedback"}, {"type" : "error", "title" : "title", "message" : "Error feedback"}, {"type" : "warning", "title" : "title", "message" : "Warning feedback"}, {"type" : "info", "title" : "title", "message" : "Info feedback"}]'
></div>
<div
class="hidden"
data-server-builder='W3sidHlwZSI6ICJjYXJkIiwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogImNhY2hlX3RpdGxlIn19LCB7InR5cGUiOiAiU3VidGl0bGUiLCAiZGF0YSI6IHsic3VidGl0bGUiOiAiY2FjaGVfc3VidGl0bGUifX0sIHsidHlwZSI6ICJGaWxlbWFuYWdlciIsICJkYXRhIjogeyJkYXRhIjogeyJiYXNlIjogeyJwYXJlbnRzIjogW10sICJjaGlsZHJlbiI6IFsiY2VydGlmaWNhdGVzIiwgImNvbmZpZ3MiLCAidGVzdCJdLCAidHlwZSI6ICJmb2xkZXIifSwgImNlcnRpZmljYXRlcyI6IHsicGFyZW50cyI6IFsiYmFzZSJdLCAiY2hpbGRyZW4iOiBbImNlcnQtMSJdLCAidHlwZSI6ICJmb2xkZXIifSwgImNlcnQtMSI6IHsicGFyZW50cyI6IFsiYmFzZSIsICJjZXJ0aWZpY2F0ZXMiXSwgImNoaWxkcmVuIjogW10sICJ0eXBlIjogImZpbGUiLCAiZG93bmxvYWRFbmRwb2ludCI6ICIvZG93bmxvYWQvY2VydC0xIn0sICJjb25maWdzIjogeyJwYXJlbnRzIjogWyJiYXNlIl0sICJjaGlsZHJlbiI6IFsiY29uZmlnLTEiXSwgInR5cGUiOiAiZm9sZGVyIn0sICJjb25maWctMSI6IHsicGFyZW50cyI6IFsiYmFzZSIsICJjb25maWdzIl0sICJjaGlsZHJlbiI6IFtdLCAidHlwZSI6ICJmaWxlIiwgImRvd25sb2FkRW5kcG9pbnQiOiAiL2Rvd25sb2FkL2NlcnQtMSJ9LCAidGVzdCI6IHsicGFyZW50cyI6IFsiYmFzZSJdLCAiY2hpbGRyZW4iOiBbInRlc3QtMSJdLCAidHlwZSI6ICJmb2xkZXIifSwgInRlc3QtMSI6IHsicGFyZW50cyI6IFsiYmFzZSIsICJ0ZXN0Il0sICJjaGlsZHJlbiI6IFsidGVzdC0yIl0sICJ0eXBlIjogImZvbGRlciIsICJkb3dubG9hZEVuZHBvaW50IjogIi9kb3dubG9hZC9jZXJ0LTEifSwgInRlc3QtMiI6IHsicGFyZW50cyI6IFsiYmFzZSIsICJ0ZXN0IiwgInRlc3QtMSJdLCAiY2hpbGRyZW4iOiBbXSwgInR5cGUiOiAiZmlsZSIsICJkb3dubG9hZEVuZHBvaW50IjogIi9kb3dubG9hZC9jZXJ0LTEifX19fV19XQ=='
></div>
<div id="app"></div>
<script type="module" src="cache.js"></script>
</body>
</html>

View file

@ -648,6 +648,9 @@ body {
.layout-settings-multiple-group {
@apply mt-4 rounded border border-gray-500/50 dark:border-gray-500 p-4 sm:gap-x-8 gap-y-8 col-span-12 grid grid-cols-12 w-full relative bg-gray-200/50 dark:bg-gray-900/10;
}
.layout-file-manager {
@apply w-full pt-6 pb-4;
}
/* MESSAGE */
.msg-container {
@ -678,6 +681,28 @@ body {
@apply fixed col-span-12 flex mx-2 my-8 w-full;
}
/* FILE MANAGER */
.file-manager-btn {
@apply h-fit col-span-12 md:col-span-6 lg:col-span-4 m-2 py-2 px-4 flex justify-between items-center bg-gray-100 dark:bg-slate-700 rounded-xl hover:brightness-90;
}
.file-manager-breadcrumb {
@apply flex justify-start items-center mx-2 mb-2;
}
.file-manager-breadcrumb-base {
@apply dark:text-gray-300 text-gray-600 text-sm mr-3;
}
.file-manager-breadcrumb-btn {
@apply dark:text-gray-300 text-gray-600 after:float-right after:pl-2 after:text-gray-600 dark:after:text-gray-300 after:content-['/'] text-sm mr-3 hover:brightness-90;
}
.file-manager-content {
@apply grid grid-cols-12 w-full min-h-60;
}
/* POPOVER */
.popover-btn {
@ -760,6 +785,10 @@ body {
@apply h-6.5 w-6.5 relative pointer-events-none;
}
.icon-file-manager {
@apply h-6.5 w-6.5 relative pointer-events-none mr-1.5;
}
.icon-list-details {
@apply ml-2 h-6.5 w-6.5 relative pointer-events-none;
}
@ -1245,6 +1274,10 @@ body {
@apply w-full col-span-12 mb-0 text-gray-700 dark:text-gray-300 break-word;
}
.text-file-manager {
@apply w-full col-span-12 flex justify-end items-center mb-0 text-gray-700 dark:text-gray-300 break-word;
}
/* BTN GROUP */
.button-group-card {
@ -1575,79 +1608,7 @@ body {
@apply col-span-12 justify-center items-center mt-4;
}
/* FILE MANAGER */
.file-manager-breadcrumb {
@apply flex flex-wrap bg-transparent rounded-lg md:mb-8 w-full;
}
.file-manager-breadcrumb-back-btn {
@apply mr-3 text-sm capitalize leading-normal dark:text-gray-500 text-gray-600 hover:brightness-75;
}
.file-manager-breadcrumb-back-svg {
@apply pointer-events-none w-4.5 h-4.5;
}
.file-manager-breadcrumb-item {
@apply leading-normal text-sm;
}
.file-manager-breadcrumb-item-btn {
@apply text-base mr-2 dark:text-gray-500 text-gray-600 after:float-right after:pl-2 after:text-gray-600 dark:after:text-gray-500 after:content-['/'] after:hover:brightness-125 hover:brightness-75;
}
.file-manager-item-container {
@apply relative w-full min-h-20 h-full transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800;
}
.file-manager-item-nav {
@apply overflow-hidden w-full cursor-pointer relative min-h-20 max-h-20 py-0.5 sm:py-1 pl-12 sm:pl-16 pr-12 sm:pr-16 text-center break-words;
}
.file-manager-item-svg {
@apply absolute left-3 top-6 sm:top-5 h-8 w-8 sm:h-10 sm:w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150;
}
.file-manager-item-name {
@apply max-h-8 pointer-events-none transition duration-300 ease-in-out dark:opacity-90 mx-7 mb-0 text-slate-700 dark:text-gray-300;
}
.base.file-manager-item-name {
@apply text-sm md:text-base;
}
.sm.file-manager-item-name {
@apply text-sm;
}
.xs.file-manager-item-name {
@apply text-[0.8rem];
}
.file-manager-item-dropdown {
@apply absolute flex-col z-110 w-48 right-0 top-0 translate-y-16 shadow border border-gray-300 dark:border-slate-600 rounded;
}
.file-manager-item-dropdown-btn {
@apply dark:brightness-125 dark:hover:brightness-100 flex justify-center items-center absolute h-full w-10 bg-primary fill-white first-letter:absolute top-0 -right-1 font-bold text-center text-white uppercase transition-all rounded-none rounded-r-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem bg-150 bg-x-25 active:opacity-85;
}
.file-manager-item-btn {
@apply duration-300 border-b border-b-gray-300 hover:brightness-90 bg-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 w-full hover:bg-gray-100;
}
.first.file-manager-item-btn {
@apply rounded-t;
}
.last.file-manager-item-btn {
@apply border-none rounded-b;
}
.file-manager-item-btn-text {
@apply transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase;
}
/* MODAL */
.modal-container {
@apply w-full h-screen fixed bg-gray-600/50 z-[10000] top-0 left-0 flex justify-center items-center;
@ -2517,7 +2478,7 @@ body {
}
.dark-darker.fill {
@apply fill-slate-600 dark:fill-slate-500;
@apply fill-slate-600 dark:fill-gray-500;
}
.amber-darker.fill {

File diff suppressed because one or more lines are too long

View file

@ -47,12 +47,18 @@ export default defineConfig({
instances: resolve(__dirname, "./dashboard/pages/instances/index.html"),
global_config: resolve(
__dirname,
"./dashboard/pages/global-config/index.html",
"./dashboard/pages/global-config/index.html"
),
configs: resolve(__dirname, "./dashboard/pages/configs/index.html"),
reports: resolve(__dirname, "./dashboard/pages/reports/index.html"),
plugins: resolve(__dirname, "./dashboard/pages/plugins/index.html"),
bans: resolve(__dirname, "./dashboard/pages/bans/index.html"),
jobs: resolve(__dirname, "./dashboard/pages/jobs/index.html"),
services: resolve(__dirname, "./dashboard/pages/services/index.html"),
modes: resolve(__dirname, "./dashboard/pages/modes/index.html"),
logs: resolve(__dirname, "./dashboard/pages/logs/index.html"),
profile: resolve(__dirname, "./dashboard/pages/profile/index.html"),
cache: resolve(__dirname, "./dashboard/pages/cache/index.html"),
},
},
},