mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
update plugins page
This commit is contained in:
parent
5ea761d735
commit
cfdde17efb
13 changed files with 466 additions and 17 deletions
|
|
@ -9,6 +9,7 @@ from .utils.widgets import (
|
|||
icons_widget,
|
||||
regular_widget,
|
||||
unmatch_widget,
|
||||
upload_widget,
|
||||
)
|
||||
from .utils.table import add_column
|
||||
from .utils.format import get_fields_from_field
|
||||
|
|
@ -47,7 +48,7 @@ def plugins_filter(types: List[str]) -> list:
|
|||
}
|
||||
]
|
||||
|
||||
if len(types) >= 2:
|
||||
if types is not None and (isinstance(types, list) and len(types) >= 2):
|
||||
filters.append(
|
||||
{
|
||||
"type": "=",
|
||||
|
|
@ -165,13 +166,12 @@ def fallback_message(msg: str, display: Optional[list] = None) -> dict:
|
|||
}
|
||||
|
||||
|
||||
def plugins_list(plugins: Optional[list] = None) -> dict:
|
||||
def plugins_list(plugins: Optional[list] = None, types: Optional[list] = None) -> dict:
|
||||
|
||||
if not plugins:
|
||||
return fallback_message(msg="plugins_not_found")
|
||||
|
||||
items = []
|
||||
types = set()
|
||||
|
||||
for plugin in plugins:
|
||||
items.append(
|
||||
|
|
@ -179,15 +179,15 @@ def plugins_list(plugins: Optional[list] = None) -> dict:
|
|||
name=plugin["id"],
|
||||
version=plugin["version"],
|
||||
description=plugin["description"],
|
||||
is_deletable=plugin["method"] == "ui",
|
||||
is_deletable=plugin["type"] in ("manual", "default", "ui", "external"),
|
||||
page=f"/plugins/{plugin['id']}" if plugin["page"] else "",
|
||||
plugin_type=plugin["type"],
|
||||
)
|
||||
)
|
||||
types.add(plugin["type"])
|
||||
|
||||
return {
|
||||
"type": "card",
|
||||
"display": ["main", 0],
|
||||
"widgets": [
|
||||
title_widget(
|
||||
title="plugins_list_title", # keep it (a18n)
|
||||
|
|
@ -200,11 +200,71 @@ def plugins_list(plugins: Optional[list] = None) -> dict:
|
|||
layout="fitColumns",
|
||||
columns=columns,
|
||||
items=items,
|
||||
filters=plugins_filter(list(types)),
|
||||
filters=plugins_filter(types),
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def plugins_builder(plugins: Optional[list] = None) -> list:
|
||||
return [plugins_list(plugins=plugins)]
|
||||
def plugins_tabs():
|
||||
return {
|
||||
"type": "tabs",
|
||||
"widgets": [
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
text="plugins_list_tab",
|
||||
display=["main", 0],
|
||||
size="tab",
|
||||
color="info",
|
||||
iconColor="white",
|
||||
iconName="list",
|
||||
),
|
||||
button_widget(
|
||||
text="plugins_upload_tab",
|
||||
color="success",
|
||||
display=["main", 1],
|
||||
size="tab",
|
||||
iconColor="white",
|
||||
iconName="plus",
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def plugins_upload():
|
||||
return {
|
||||
"type": "card",
|
||||
"display": ["main", 1],
|
||||
"widgets": [
|
||||
title_widget(
|
||||
title="plugins_upload_title", # keep it (a18n)
|
||||
),
|
||||
subtitle_widget(
|
||||
subtitle="plugins_upload_subtitle", # keep it (a18n)
|
||||
),
|
||||
upload_widget(
|
||||
maxScreenW="sm",
|
||||
),
|
||||
button_group_widget(
|
||||
buttons=[
|
||||
button_widget(
|
||||
text="action_reload", # keep it (a18n)
|
||||
color="info",
|
||||
size="normal",
|
||||
attrs={
|
||||
"data-submit-data": "{}",
|
||||
"data-submit-endpoint": "",
|
||||
},
|
||||
disabled=True,
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def plugins_builder(plugins: Optional[list] = None, types: Optional[list] = None) -> list:
|
||||
return [plugins_tabs(), plugins_list(plugins=plugins, types=types), plugins_upload()]
|
||||
|
|
|
|||
|
|
@ -2133,6 +2133,7 @@ def templates_widget(
|
|||
templates: dict,
|
||||
operation: str = "edit",
|
||||
oldServerName: str = "",
|
||||
isDraft: Union[str, bool] = False,
|
||||
display: Optional[list] = None
|
||||
):
|
||||
"""
|
||||
|
|
@ -2143,6 +2144,7 @@ def templates_widget(
|
|||
- `templates` **Object** List of advanced templates that contains settings. Must be a dict with mode as key, then the template name as key with a list of data (different for each modes).
|
||||
- `operation` **String** Operation type (edit, new, delete). (optional, default `"edit"`)
|
||||
- `oldServerName` **String** Old server name. This is a server name before any changes. (optional, default `""`)
|
||||
- `isDraft` **(String | Boolean)** Is draft mode. "yes" or "no" to set a draft select. Else will be ignored. (optional, default `false`)
|
||||
- `display` **Array** Array need two values : "groupName" in index 0 and "compId" in index 1 in order to be displayed using the display store. More info on the display store itslef. (optional, default `[]`)
|
||||
|
||||
EXAMPLE
|
||||
|
|
@ -2166,7 +2168,7 @@ def templates_widget(
|
|||
|
||||
|
||||
# List of params that will be add only if not default value
|
||||
list_params = [("operation", operation, "edit"),("oldServerName", oldServerName, ""),("display", display, None)]
|
||||
list_params = [("operation", operation, "edit"),("oldServerName", oldServerName, ""),("isDraft", isDraft, False),("display", display, None)]
|
||||
for param in list_params:
|
||||
add_key_value(data, param[0], param[1], param[2])
|
||||
|
||||
|
|
@ -2310,3 +2312,39 @@ def unmatch_widget(
|
|||
|
||||
return { "type" : "Unmatch", "data" : data }
|
||||
|
||||
|
||||
def upload_widget(
|
||||
disabled: bool = False,
|
||||
columns: dict = {"pc":"12","tablet":"12","mobile":"12"},
|
||||
containerClass: str = "",
|
||||
maxScreenW: str = "2xl"
|
||||
):
|
||||
"""
|
||||
This component is used to upload files to the server. ATM only used to upload plugins.
|
||||
|
||||
PARAMETERS
|
||||
|
||||
- `disabled` **Boolean** If true, the upload will be disabled. (optional, default `False`)
|
||||
- `columns` **Object** Columns object. (optional, default `{"pc":"12","tablet":"12","mobile":"12"}`)
|
||||
- `containerClass` **String** Container additional class (optional, default `""`)
|
||||
- `maxScreenW` **String** Max screen width within sm, md, lg, xl, 2xl, 3xl (optional, default `"2xl"`)
|
||||
|
||||
EXAMPLE
|
||||
|
||||
{
|
||||
disabled : True
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
data = {
|
||||
}
|
||||
|
||||
|
||||
# List of params that will be add only if not default value
|
||||
list_params = [("disabled", disabled, False),("columns", columns, {"pc":"12","tablet":"12","mobile":"12"}),("containerClass", containerClass, ""),("maxScreenW", maxScreenW, "2xl")]
|
||||
for param in list_params:
|
||||
add_key_value(data, param[0], param[1], param[2])
|
||||
|
||||
return { "type" : "Upload", "data" : data }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ from utils import save_builder
|
|||
from pages.plugins import plugins_builder
|
||||
|
||||
plugins = [
|
||||
{"name": "plugin1", "version": "1.0.0", "description": "This is plugin1", "type": "core", "is_deletable": False, "page": "/mypage"},
|
||||
{"name": "plugin2", "version": "1.0.0", "description": "This is plugin2", "type": "external", "is_deletable": False, "page": "/mypag1"},
|
||||
{"name": "plugin3", "version": "1.0.0", "description": "This is plugin3", "type": "pro", "is_deletable": True, "page": ""},
|
||||
{"name": "plugin4", "version": "1.0.0", "description": "This is plugin4", "type": "pro", "is_deletable": True, "page": ""},
|
||||
{"id": "plugin1", "name": "plugin1", "version": "1.0.0", "description": "This is plugin1", "type": "core", "is_deletable": False, "page": "/mypage"},
|
||||
{"id": "plugin2", "name": "plugin2", "version": "1.0.0", "description": "This is plugin2", "type": "external", "is_deletable": False, "page": "/mypag1"},
|
||||
{"id": "plugin3", "name": "plugin3", "version": "1.0.0", "description": "This is plugin3", "type": "pro", "is_deletable": True, "page": ""},
|
||||
{"id": "plugin4", "name": "plugin4", "version": "1.0.0", "description": "This is plugin4", "type": "pro", "is_deletable": True, "page": ""},
|
||||
]
|
||||
|
||||
types = ["core", "external", "pro"]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import GridLayout from "@components/Widget/GridLayout.vue";
|
|||
import ListDetails from "@components/List/Details.vue";
|
||||
import Title from "@components/Widget/Title.vue";
|
||||
import Subtitle from "@components/Widget/Subtitle.vue";
|
||||
import Upload from "@components/Widget/Upload.vue";
|
||||
import Text from "@components/Widget/Text.vue";
|
||||
import Tabulator from "@components/Widget/Tabulator.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
|
|
@ -75,6 +76,10 @@ const props = defineProps({
|
|||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<Title v-if="useEqualStr(widget.type, 'Title')" v-bind="widget.data" />
|
||||
<Upload
|
||||
v-if="useEqualStr(widget.type, 'Upload')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Subtitle
|
||||
v-if="useEqualStr(widget.type, 'Subtitle')"
|
||||
v-bind="widget.data"
|
||||
|
|
|
|||
313
src/ui/client/dashboard/components/Widget/Upload.vue
Normal file
313
src/ui/client/dashboard/components/Widget/Upload.vue
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import DOMPurify from "dompurify";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
|
||||
/**
|
||||
* @name Widget/Upload.vue
|
||||
* @description This component is used to upload files to the server. ATM only used to upload plugins.
|
||||
* @example
|
||||
* {
|
||||
* disabled : True
|
||||
* }
|
||||
* @param {Boolean} [disabled=False] - If true, the upload will be disabled.
|
||||
* @param {Object} [columns={ "pc": "12", "tablet": "12", "mobile": "12" }] - Columns object.
|
||||
* @param {String} [containerClass=""] - Container additional class
|
||||
* @param {String} [maxScreenW="2xl"] - Max screen width within sm, md, lg, xl, 2xl, 3xl
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: { pc: "12", tablet: "12", mobile: "12" },
|
||||
},
|
||||
maxScreenW: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "2xl",
|
||||
},
|
||||
});
|
||||
|
||||
const containerEl = ref(null);
|
||||
|
||||
class Upload {
|
||||
constructor(containerEl) {
|
||||
this.container = containerEl;
|
||||
this.form = containerEl.querySelector("#dropzone-form");
|
||||
this.dropZoneElement = containerEl.querySelector(".drop-zone");
|
||||
this.fileInput = containerEl.querySelector(".file-input");
|
||||
this.progressArea = containerEl.querySelector(".progress-area");
|
||||
this.uploadedArea = containerEl.querySelector(".uploaded-area");
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
//form click launch input file
|
||||
this.form.addEventListener("click", () => {
|
||||
this.fileInput.click();
|
||||
});
|
||||
//dropzone logic
|
||||
this.dropZoneElement.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
this.dragInStyle();
|
||||
});
|
||||
|
||||
["dragleave", "dragend"].forEach((type) => {
|
||||
this.dropZoneElement.addEventListener(type, (e) => {
|
||||
this.dragOutStyle();
|
||||
});
|
||||
});
|
||||
|
||||
this.dropZoneElement.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
this.fileInput.files = e.dataTransfer.files;
|
||||
this.fileInput.dispatchEvent(new Event("change"));
|
||||
this.dragOutStyle();
|
||||
});
|
||||
//when added file, set upload logic
|
||||
this.fileInput.addEventListener("change", () => {
|
||||
this.dragOutStyle();
|
||||
const timeout = 500;
|
||||
for (let i = 0; i < this.fileInput.files.length; i++) {
|
||||
setTimeout(() => this.uploadFile(this.fileInput.files[i]), timeout * i);
|
||||
}
|
||||
});
|
||||
|
||||
//close fail/success log
|
||||
this.container.addEventListener("click", (e) => {
|
||||
try {
|
||||
if (
|
||||
e.target.closest("button").hasAttribute("data-upload-message-delete")
|
||||
) {
|
||||
const message = e.target.closest("div[data-upload-message]");
|
||||
message.remove();
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
dragOutStyle() {
|
||||
this.dropZoneElement.classList.remove(
|
||||
"border-solid",
|
||||
"bg-gray-100",
|
||||
"dark:bg-slate-700/50"
|
||||
);
|
||||
this.dropZoneElement.classList.add("border-dashed");
|
||||
}
|
||||
|
||||
dragInStyle() {
|
||||
this.dropZoneElement.classList.add(
|
||||
"border-solid",
|
||||
"bg-gray-100",
|
||||
"dark:bg-slate-700/50"
|
||||
);
|
||||
this.dropZoneElement.classList.remove("border-dashed");
|
||||
}
|
||||
|
||||
uploadFile(file) {
|
||||
let name = file.name;
|
||||
if (name.length >= 12) {
|
||||
let splitName = name.split(".");
|
||||
name = splitName[0].substring(0, 13) + "... ." + splitName[1];
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "plugins/upload");
|
||||
let fileSize;
|
||||
|
||||
xhr.upload.addEventListener("progress", ({ loaded, total }) => {
|
||||
let fileTotal = Math.floor(total / 1000);
|
||||
|
||||
fileTotal < 1024
|
||||
? (fileSize = fileTotal + " KB")
|
||||
: (fileSize = (loaded / (1024 * 1024)).toFixed(2) + " MB");
|
||||
|
||||
const progressHTML = this.fileLoad(name, fileSize);
|
||||
let cleanHTML = DOMPurify.sanitize(progressHTML);
|
||||
|
||||
this.uploadedArea.classList.add("onprogress");
|
||||
this.progressArea.innerHTML = cleanHTML;
|
||||
});
|
||||
|
||||
xhr.addEventListener("readystatechange", () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
this.progressArea.innerHTML = "";
|
||||
|
||||
if (xhr.status == 201) {
|
||||
this.uploadedArea.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
this.fileSuccess(name, fileSize)
|
||||
);
|
||||
this.allowReload();
|
||||
} else {
|
||||
this.uploadedArea.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
this.fileFail(name, fileSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let data = new FormData();
|
||||
data.set("file", file);
|
||||
data.set(
|
||||
"csrf_token",
|
||||
document
|
||||
.querySelector("[data-csrf-token]")
|
||||
.getAttribute("data-csrf-token")
|
||||
);
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
allowReload() {
|
||||
const reloadBtn = document.querySelector("[data-reload-btn]");
|
||||
reloadBtn.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
fileLoad(name, fileSize) {
|
||||
const str = `<div class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="fill-sky-500 stroke-sky-500 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
||||
|
||||
<svg class=" fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-3 w-3 " viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return str;
|
||||
}
|
||||
|
||||
fileSuccess(name, fileSize) {
|
||||
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg
|
||||
class="fill-green-500 h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
||||
<button type="button" data-upload-message-delete>
|
||||
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
let cleanHTML = DOMPurify.sanitize(str);
|
||||
return cleanHTML;
|
||||
}
|
||||
|
||||
fileFail(name, fileSize) {
|
||||
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg
|
||||
class="fill-red-500 h-5 w-5 mr-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
||||
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
||||
<button type="button" data-upload-message-delete>
|
||||
|
||||
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
let cleanHTML = DOMPurify.sanitize(str);
|
||||
return cleanHTML;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
new Upload(containerEl.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container
|
||||
:containerClass="`${props.containerClass} flex justify-center`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
<div
|
||||
ref="containerEl"
|
||||
:class="['upload-container', `max-w-screen-${props.maxScreenW}`]"
|
||||
>
|
||||
<h5 class="upload-title">UPLOAD / RELOAD</h5>
|
||||
<div class="upload-dropzone-container">
|
||||
<!-- dropzone -->
|
||||
<form
|
||||
id="dropzone-form"
|
||||
action="#"
|
||||
:class="[
|
||||
props.disabled
|
||||
? 'cursor-not-allowed'
|
||||
: 'cursor-pointer hover:bg-gray-100 dark:hover:bg-slate-700/50',
|
||||
'upload-dropzone-form drop-zone',
|
||||
]"
|
||||
>
|
||||
<input
|
||||
:disabled="props.disabled"
|
||||
class="file-input drop-zone__input"
|
||||
type="file"
|
||||
name="file"
|
||||
multiple="multiple"
|
||||
hidden
|
||||
/>
|
||||
<div class="flex justify-center mt-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
:class="['h-7 w-7', props.disabled ? '' : 'fill-primary']"
|
||||
>
|
||||
<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.53 5.47a.75.75 0 0 0-1.06 0l-3 3a.75.75 0 1 0 1.06 1.06l1.72-1.72v5.69a.75.75 0 0 0 1.5 0v-5.69l1.72 1.72a.75.75 0 1 0 1.06-1.06l-3-3Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p class="dark:text-gray-500 text-sm text-center mb-3">
|
||||
{{
|
||||
props.disabled ? "upload not available" : "click or drag and drop"
|
||||
}}
|
||||
</p>
|
||||
</form>
|
||||
<div class="col-span-12 progress-area"></div>
|
||||
<div class="col-span-12 uploaded-area"></div>
|
||||
<!-- end dropzone -->
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</template>
|
||||
|
|
@ -255,6 +255,10 @@
|
|||
"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_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",
|
||||
|
|
|
|||
|
|
@ -152,8 +152,6 @@ function addBanHandler() {
|
|||
.querySelector(`#${tableAddbanId}`)
|
||||
.querySelectorAll(".tabulator-row");
|
||||
|
||||
console.log();
|
||||
|
||||
for (let i = 0; i < tabulatorRows.length; i++) {
|
||||
const row = tabulatorRows[i];
|
||||
const ip = row.querySelector("input[name=ip]").value;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { reactive, onBeforeMount, onMounted } from "vue";
|
|||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
import BuilderPlugins from "@components/Builder/Plugins.vue";
|
||||
import { useGlobal } from "@utils/global.js";
|
||||
import { useDisplayStore } from "@store/global.js";
|
||||
|
||||
/**
|
||||
* @name Page/PLugins.vue
|
||||
|
|
@ -10,6 +11,10 @@ import { useGlobal } from "@utils/global.js";
|
|||
This page displays global information about plugins, and allow to delete or upload some plugins.
|
||||
*/
|
||||
|
||||
// Set default store
|
||||
const displayStore = useDisplayStore();
|
||||
displayStore.setDisplay("main", 0);
|
||||
|
||||
const plugins = reactive({
|
||||
builder: "",
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -557,6 +557,24 @@ body {
|
|||
@apply flex scale-110 h-5 w-5 items-center align-middle;
|
||||
}
|
||||
|
||||
/* UPLOAD */
|
||||
|
||||
.upload-container {
|
||||
@apply p-4 w-full col-span-12 md:col-span-7 2xl:col-span-4 grid grid-cols-12 relative min-w-0 break-words bg-white dark:bg-slate-850 bg-clip-border;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
@apply col-span-12 mb-4 font-bold dark:text-gray-100;
|
||||
}
|
||||
|
||||
.upload-dropzone-container {
|
||||
@apply mx-2 p-0 col-span-12 grid grid-cols-12;
|
||||
}
|
||||
|
||||
.upload-dropzone-form {
|
||||
@apply col-span-12 border-2 rounded-lg p-2 border-dashed border-primary dark:brightness-125;
|
||||
}
|
||||
|
||||
/* LAYOUT */
|
||||
|
||||
.layout-grid {
|
||||
|
|
|
|||
7
src/ui/client/package-lock.json
generated
7
src/ui/client/package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
|||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"ace-builds": "^1.24.2",
|
||||
"dompurify": "^3.1.6",
|
||||
"flag-icons": "^6.15.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"pinia": "^2.1.6",
|
||||
|
|
@ -1333,6 +1334,12 @@
|
|||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
|
||||
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.508",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"ace-builds": "^1.24.2",
|
||||
"dompurify": "^3.1.6",
|
||||
"flag-icons": "^6.15.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"pinia": "^2.1.6",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue