change ui folder names and vite build to avoid conflicts

This commit is contained in:
Jordan Blasenhauer 2024-07-01 16:37:54 +02:00
parent d17ebca163
commit 5222c91fd9
33 changed files with 271 additions and 123 deletions

View file

@ -228,13 +228,12 @@ function formatMd() {
// And delete output folder
function moveMd() {
fs.rename(`./output/${finalFile}`, `./${finalFile}`, function (err) {
if (err) throw err
console.log('Successfully renamed - AKA moved!')
})
fs.rename(`./output/${finalFile}`, `./${finalFile}`, function (err) {
if (err) throw err
})
// Delete output folder
fs.rmdirSync(ouputFolder, { recursive: true });
// Delete output folder
fs.rmdirSync(ouputFolder, { recursive: true });
}

View file

@ -2,12 +2,62 @@ const { execSync } = require("child_process");
const { resolve } = require("path");
const fs = require("fs");
const frontDir = "/vite";
const clientBuildDir = "static";
const setupBuildDir = "setup/output";
const appStaticDir = "../ui/static";
const appTempDir = "../ui/templates";
async function moveFile(src, dest) {
fs.renameSync(src, dest, (err) => {
if (err) {
return console.error(err);
}
});
}
async function createDir(dir) {
fs.promises
.access(dir, fs.constants.F_OK)
.then(() => true)
.catch(() =>
fs.mkdir(dir, (err) => {
if (err) {
return console.error(err);
}
})
);
}
async function deleteDir(dir) {
fs.rm(
dir,
{
recursive: true,
},
(error) => {
if (error) {
console.log(error);
} else {
}
}
);
}
async function copyDir(src, dest) {
fs.cpSync(src, dest, { recursive: true }, (err) => {
/* callback */
});
}
async function copyFile(src, dest) {
fs.copyFileSync(src, dest, { recursive: true }, (err) => {
/* callback */
});
}
// Run subprocess command on specific dir
function runCommand(dir, command) {
let isErr = false;
async function runCommand(dir, command) {
try {
execSync(
command,
@ -16,118 +66,94 @@ function runCommand(dir, command) {
console.log(stdout);
console.log(stderr);
if (err !== null) {
isErr = true;
console.log(`exec error: ${err}`);
}
}
);
} catch (err) {
isErr = true;
console.log(err);
}
return isErr;
}
// Install deps and build vite (work for client and setup)
function buildVite(dir) {
let isErr = false;
async function buildVite(dir) {
// Install packages
isErr = runCommand(dir, "npm install");
if (isErr) return isErr;
// Build vite
isErr = runCommand(dir, "npm run build");
return isErr;
await runCommand(dir, "npm install");
await runCommand(dir, "npm run build");
}
// Change dir structure for flask app
function updateClientDir() {
let isErr = false;
async function updateClientDir() {
const srcDir = resolve(`./${clientBuildDir}/src/pages`);
const destDir = resolve(`./${clientBuildDir}/templates`);
const dirToRem = resolve(`./${clientBuildDir}/src`);
const staticTemp = resolve(`./${clientBuildDir}/templates`);
try {
// Change dir position for html
fs.cpSync(srcDir, destDir, {
force: true,
recursive: true,
});
const changeDirHtml = await copyDir(srcDir, staticTemp);
// Remove prev dir
fs.rmSync(dirToRem, { recursive: true, force: true });
// Change templates/page/index.html by templates/{page_name}.html
// And move from static to templates
const templateDir = resolve(`./${clientBuildDir}/templates`);
const removePrevDir = await deleteDir(dirToRem);
// Create template dir if not exist
if (!fs.existsSync(resolve("./templates"))) {
fs.mkdirSync(resolve("./templates"));
}
fs.readdir(templateDir, (err, subdirs) => {
subdirs.forEach((subdir) => {
// Get absolute path of current subdir
const currPath = resolve(`./${clientBuildDir}/templates/${subdir}`);
// Rename index.html by subdir name
fs.renameSync(`${currPath}/index.html`, `${currPath}/${subdir}.html`);
// Copy file to move it from /template/page to /template
fs.copyFileSync(
`${currPath}/${subdir}.html`,
resolve(`./templates/${subdir}.html`)
);
});
});
// Delete useless dir
fs.rmSync(currPath, { recursive: true, force: true });
fs.rmSync(`./${clientBuildDir}/templates/`, {
recursive: true,
force: true,
});
const createTemp = await createDir("./templates");
// Change output templates
const changeOutputTemp = await changeOutputTemplates();
const removeTemp = await deleteDir(staticTemp);
} catch (err) {
isErr = true;
console.log(err);
}
return isErr;
}
function setFlaskData() {
async function changeOutputTemplates() {
const templateDir = resolve(`./${clientBuildDir}/templates`);
console.log(templateDir);
fs.readdir(templateDir, async (err, subdirs) => {
subdirs.forEach(async (subdir) => {
// Get absolute path of current subdir
const currPath = resolve(`./${clientBuildDir}/templates/${subdir}`);
// Rename index.html by subdir name
await moveFile(
`${currPath}/index.html`,
resolve(`./templates/${subdir}.html`)
);
});
});
}
async function setFlaskData() {
// Run all files in /templates and get data
fs.readdir(resolve("./templates"), (err, files) => {
// Read content
files.forEach((file) => {
let updateData = "";
const data = fs.readFileSync(resolve(`./templates/${file}`), {
encoding: "utf8",
flag: "r",
});
try {
// match every attribute starting with data- and ending with a ' or a "
const matches = data.match(/data-[^"']+["']/g);
// remove content between <body> and </body>
updateData = data.replace(/<body>[\s\S]*<\/body>/g, "");
// get the <body> index to insert the new content
const bodyIndex = data.indexOf("<body>");
const data = fs.readFile(
resolve(`./templates/${file}`),
{
encoding: "utf8",
flag: "r",
},
(err, data) => {
if (err) {
console.log(err);
}
let updateData = "";
// remove everything after <body> tag
const bodyIndex = data.indexOf("<body>");
// Add attributs
let attributs = "";
matches.forEach((match) => {
const matchFormat = match.replace('="', "").replace("='", "");
attributs += `<div class="hidden" ${matchFormat}={{${matchFormat.replaceAll(
"-",
"_"
)}}}></div>\n`;
});
// insert the new content
updateData =
data.slice(0, bodyIndex) +
`\n<body>\n` +
`<div class="hidden" data-csrf-token={{ csrf_token() }}></div>\n` +
attributs +
`<div id="app"></div>\n</body>\n</html>`;
} catch (e) {
console.log(e);
updateData = "";
}
// Write the new content to the file
if (updateData)
fs.writeFileSync(resolve(`./templates/${file}`), updateData, "utf8");
const attributs = `<body>
<div class="hidden" data-csrf-token={{ csrf_token() }}></div>\n
<div class="hidden" data-server-global={{data_server_global}}></div>\n
<div class="hidden" data-server-flash={{data_server_flash}}></div>\n
<div class="hidden" data-server-builder={{data_server_builder}}></div>\n
<div id="app"></div>\n</body>\n</html>`;
// insert the new content
updateData = updateData = data.substring(0, bodyIndex) + attributs;
fs.writeFile(
`${appTempDir}/${file}`,
updateData,
"utf8",
(err) => {}
);
}
);
});
});
}
@ -147,14 +173,39 @@ function setSetup() {
return isErr;
}
// Build client and setup
const buildClientErr = buildVite("/client");
if (buildClientErr)
console.log("Error while building client. Impossible to continue.");
// Change client dir structure
const isUpdateDirErr = updateClientDir();
if (isUpdateDirErr)
console.log(
"Error while changing client dir structure. Impossible to continue."
);
const setFlskData = setFlaskData();
async function moveDir() {
// move build static subdir to app ui static dir
const srcDir = resolve(`./static`);
const destDir = resolve(appStaticDir);
fs.readdir(srcDir, (err, dirs) => {
dirs.forEach((dir) => {
fs.rmSync(`${destDir}/${dir}`, { recursive: true }, (err) => {
if (err) {
console.log(err);
}
});
fs.renameSync(
`${srcDir}/${dir}`,
`${destDir}/${dir}`,
{ recursive: true },
(err) => {
if (err) {
console.log(err);
}
}
);
});
});
}
async function build() {
// Build client and setup
const build = await buildVite(frontDir);
// Change client dir structure
const update = await updateClientDir();
const setFlskData = await setFlaskData();
const moveDirs = await moveDir();
}
build();

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View file

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,6 +1,6 @@
<script setup>
import { onMounted, reactive, ref } from "vue";
import logoMenu2 from "@public/images/logo-menu-2.png";
import logoMenu2 from "@public/img/logo-menu-2.png";
/**
@name Dashboard/Loader.vue

View file

@ -3,8 +3,8 @@ import Icons from "@components/Widget/Icons.vue";
import { reactive, onMounted, onBeforeMount } from "vue";
import { menuIndex, menuFloatIndex } from "@/utils/tabindex.js";
import { useBannerStore } from "@store/global.js";
import logoMenu2 from "@public/images/logo-menu-2.png";
import logoMenu from "@public/images/logo-menu.png";
import logoMenu2 from "@public/img/logo-menu-2.png";
import logoMenu from "@public/img/logo-menu.png";
/**
@name Dashboard/Menu.vue

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
<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" />

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 | DASHBOARD</title>
<script type="module" crossorigin src="/assets/global-config-ecc5cef1.js"></script>
<link rel="modulepreload" crossorigin href="/assets/Layout-648879ba.js">
<link rel="modulepreload" crossorigin href="/assets/Title-d64c1339.js">
<link rel="modulepreload" crossorigin href="/assets/Text-6e4ad67c.js">
<link rel="modulepreload" crossorigin href="/assets/form-41d8fb86.js">
<link rel="stylesheet" href="/assets/index-4b52d92d.css">
</head>
<body>
<div class="hidden" data-server-global='{"username" : "admin"}'></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='[{"type":"card","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"bunkerweb"},{"key":"instances_type","value":"manual"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"bunkerweb","buttons":[{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"reload","data-submit-form":"true"},"text":"action_reload","color":"warning","size":"normal"},{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"stop","data-submit-form":"true"},"text":"action_stop","color":"error","size":"normal"}]}}]}]'>
</div>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!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 | DASHBOARD</title>
<script type="module" crossorigin src="/assets/home-e8f094cf.js"></script>
<link rel="modulepreload" crossorigin href="/assets/Layout-648879ba.js">
<link rel="modulepreload" crossorigin href="/assets/Title-d64c1339.js">
<link rel="modulepreload" crossorigin href="/assets/Text-6e4ad67c.js">
</head>
<body>
<div class="hidden" data-server-global='{"username" : "admin"}'></div>
<div class="hidden"
data-server-flash='[{"type" : "success", "title" : "success", "message" : "Success feedback"}, {"type" : "error", "title" : "error", "message" : "Error feedback"}, {"type" : "warning", "title" : "warning", "message" : "Warning feedback"}, {"type" : "info", "title" : "info", "message" : "Info feedback"}]'>
</div>
<div class="hidden"
data-server-builder='[{"type":"card","link":"https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_version","subtitle":"home_upgrade_to_pro","subtitleColor":"warning","stat":"home_free","iconName":"key"}}]},{"type":"card","link":"https://github.com/bunkerity/bunkerweb","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_version_number","subtitle":"home_update_available","subtitleColor":"warning","stat":"1.5.8","iconName":"wire"}}]},{"type":"card","link":"/instances","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_instances","subtitle":"home_total_number","subtitleColor":"info","stat":1,"iconName":"box"}}]},{"type":"card","link":"/services","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_services","subtitle":"home_all_methods_included","subtitleColor":"info","stat":2,"iconName":"disk"}}]},{"type":"card","link":"/plugins","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_plugins","subtitle":"home_no_error","subtitleColor":"success","stat":"42","iconName":"puzzle"}}]}]'>
</div>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!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 | DASHBOARD</title>
<script type="module" crossorigin src="/assets/instances-60b6359f.js"></script>
<link rel="modulepreload" crossorigin href="/assets/Layout-648879ba.js">
<link rel="modulepreload" crossorigin href="/assets/Title-d64c1339.js">
<link rel="modulepreload" crossorigin href="/assets/form-41d8fb86.js">
</head>
<body>
<div class="hidden" data-server-global='{"username" : "admin"}'></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='[{"type":"card","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"pairs":[{"key":"instances_hostname","value":"bunkerweb"},{"key":"instances_type","value":"manual"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"bunkerweb","buttons":[{"attrs":{"data-submit-form": {"operation" : "reload", "INSTANCE_ID" : "bunkerweb"} },"text":"action_reload","color":"warning","size":"normal"},{"attrs":{"data-submit-form": { "operation" : "stop", "INSTANCE_ID" : "bunkerweb"} },"text":"action_stop","color":"error","size":"normal"}]}}]}]'>
</div>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!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 | TEST</title>
<script type="module" crossorigin src="/assets/test-56d5313a.js"></script>
<link rel="modulepreload" crossorigin href="/assets/Layout-648879ba.js">
</head>
<body>
<div data-default-value="Title default !" data-flask="{{ flask_data }}" class="hidden"></div>
<div id="app"></div>
</body>
</html>

View file

@ -10,7 +10,7 @@
<title>BunkerWeb UI</title>
<link rel="icon" type="image/x-icon" href="images/favicon.ico" />
<!-- tailwind style -->
<link rel="stylesheet" href="css/dashboard.css" />
<link rel="stylesheet" href="style/dashboard.css" />
<script type="module" src="js/global.js" nonce="{{ script_nonce }}"></script>
<script src="js/plugins/utils.js" nonce="{{ script_nonce }}"></script>
<script async src="js/utils/purify/purify.min.js" nonce="{{ script_nonce }}"></script>
@ -26,11 +26,11 @@
{% elif current_endpoint == "cache" %}
<script type="module" src="js/cache.js" nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "logs" %}
<link rel="stylesheet" href="css/flatpickr.css" />
<link rel="stylesheet" href="css/flatpickr.dark.css" />
<link rel="stylesheet" href="style/flatpickr.css" />
<link rel="stylesheet" href="style/flatpickr.dark.css" />
<script defer src="js/utils/flatpickr.js" nonce="{{ script_nonce }}"></script>
<script type="module" src="js/logs.js" nonce="{{ script_nonce }}"></script>
<link rel="stylesheet" href="css/datepicker-foundation.css" />
<link rel="stylesheet" href="style/datepicker-foundation.css" />
{% elif current_endpoint == "jobs" %}
<script type="module" src="js/jobs.js" nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "account" %}
@ -38,8 +38,8 @@
{% elif current_endpoint == "reports" %}
<script type="module" src="js/reports.js" nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "bans" %}
<link rel="stylesheet" href="css/flatpickr.css" />
<link rel="stylesheet" href="css/flatpickr.dark.css" />
<link rel="stylesheet" href="style/flatpickr.css" />
<link rel="stylesheet" href="style/flatpickr.dark.css" />
<script defer src="js/utils/flatpickr.js" nonce="{{ script_nonce }}"></script>
<script type="module" src="js/bans.js" nonce="{{ script_nonce }}"></script>
{% endif %}