mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
start adding dashboard layout
This commit is contained in:
parent
506023afc0
commit
4ca0e5d60c
14 changed files with 1035 additions and 7 deletions
|
|
@ -27,7 +27,7 @@ We need to work on the front-end here, then build and move to flask app the reso
|
|||
- Run `flask --app main.py run` then access `/test` to get Vue.js rendering app using flask data `Title from Flask !`
|
||||
- `/test2` is case with no flask data (can be handle on a div or directly on Vue app)
|
||||
|
||||
# jsdoc
|
||||
# jsdoc (render doc from .vue components)
|
||||
|
||||
We can create a markdown documentation for components.
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ body {
|
|||
}
|
||||
|
||||
.input-regular {
|
||||
@apply hover:border-gray-600 outline-none dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-gray-300/0 dark:focus:border-gray-600/0 focus:ring-1 focus:valid:ring-green-500 focus:invalid:ring-red-500 text-sm leading-5.6 ease-in block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-2.5 md:py-1.5 font-normal text-gray-700 transition-all placeholder:text-gray-500 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-800 dark:disabled:border-gray-800/0 dark:disabled:text-gray-300 disabled:text-gray-700;
|
||||
@apply hover:border-gray-600 outline-none dark:border-slate-600 dark:bg-slate-700 dark:text-gray-200 focus:border-gray-300/0 dark:focus:border-gray-600/0 focus:ring-1 focus:valid:ring-green-500 focus:invalid:ring-red-500 text-sm leading-5.6 ease-in block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-2.5 md:py-1.5 font-normal text-gray-700 transition-all placeholder:text-gray-500 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-800 dark:disabled:border-gray-800/0 dark:disabled:text-gray-300 disabled:text-gray-700;
|
||||
}
|
||||
|
||||
.invalid.input-regular,
|
||||
|
|
@ -475,7 +475,7 @@ body {
|
|||
}
|
||||
|
||||
.menu-container {
|
||||
@apply transition-all fixed flex inset-y-0 flex-wrap justify-between w-full p-0 my-4 overflow-y-auto antialiased duration-200 bg-white border-0 shadow-xl dark:shadow-none dark:bg-slate-850 dark:brightness-110 max-w-64 z-[1000] xl:ml-6 rounded-2xl xl:left-0;
|
||||
@apply transition-all mt-[4.5rem] fixed flex flex-col justify-between inset-y-0 max-h-screen w-full p-0 my-4 antialiased duration-200 -translate-x-full bg-white border-0 shadow-xl dark:shadow-none dark:bg-slate-850 dark:brightness-110 max-w-64 z-[1000] xl:ml-6 rounded-2xl xl:left-0 xl:translate-x-0;
|
||||
}
|
||||
|
||||
.no-banner.menu-container {
|
||||
|
|
@ -518,6 +518,10 @@ body {
|
|||
@apply h-[45vh];
|
||||
}
|
||||
|
||||
.menu-top-content {
|
||||
@apply mt-6;
|
||||
}
|
||||
|
||||
.menu-nav-list {
|
||||
@apply flex flex-col pl-0 mb-0;
|
||||
}
|
||||
|
|
@ -538,8 +542,16 @@ body {
|
|||
@apply ml-1 duration-300 opacity-100 pointer-events-none capitalize-first;
|
||||
}
|
||||
|
||||
.menu-page-plugin-item-title {
|
||||
@apply w-full mt-4;
|
||||
}
|
||||
|
||||
.menu-page-plugin-item-page {
|
||||
@apply mt-0.5 w-full;
|
||||
}
|
||||
|
||||
.menu-page-plugin-title {
|
||||
@apply pl-6 ml-2 text-xs font-bold leading-tight uppercase dark:text-white opacity-60;
|
||||
@apply pl-6 ml-2 text-xs font-bold leading-tight uppercase dark:text-gray-400 dark:opacity-100 opacity-60;
|
||||
}
|
||||
|
||||
.menu-page-plugin-empty-title {
|
||||
|
|
@ -551,15 +563,19 @@ body {
|
|||
}
|
||||
|
||||
.menu-page-plugin-anchor {
|
||||
@apply dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm my-0 mx-2 flex items-center whitespace-nowrap px-4 transition;
|
||||
@apply dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-gray-200 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition;
|
||||
}
|
||||
|
||||
.menu-page-plugin-svg-container {
|
||||
@apply mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5;
|
||||
@apply mr-2 flex flex-wrap items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5;
|
||||
}
|
||||
|
||||
.menu-page-plugin-name {
|
||||
@apply ml-1 duration-300 opacity-100 pointer-events-none;
|
||||
@apply ml-1 duration-300 opacity-100 pointer-events-none ease;
|
||||
}
|
||||
|
||||
.menu-bottom-content {
|
||||
@apply w-full flex flex-col justify-end mt-2 m-4;
|
||||
}
|
||||
|
||||
.menu-mode-container {
|
||||
|
|
@ -578,6 +594,10 @@ body {
|
|||
@apply flex justify-center align-middle w-full mb-4;
|
||||
}
|
||||
|
||||
.menu-social-list-item {
|
||||
@apply mx-2 w-6;
|
||||
}
|
||||
|
||||
.menu-logout {
|
||||
@apply tracking-wide dark:brightness-125 hover:brightness-75 w-full inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-gradient-to-tl bg-primary leading-normal text-xs ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md;
|
||||
}
|
||||
|
|
|
|||
88
vuejs/client/src/components/Dashboard/Banner.vue
Normal file
88
vuejs/client/src/components/Dashboard/Banner.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive } from "vue";
|
||||
import { useBannerStore } from "@store/global.js";
|
||||
import { bannerIndex } from "@utils/tabindex.js";
|
||||
const bannerStore = useBannerStore();
|
||||
|
||||
const banner = reactive({
|
||||
visibleId: 1,
|
||||
});
|
||||
|
||||
function setupBanner() {
|
||||
const nextDelay = 14000;
|
||||
const transDuration = 10000;
|
||||
// Switch item every interval and
|
||||
setInterval(() => {
|
||||
const prev = banner.visibleId;
|
||||
banner.visibleId = banner.visibleId === 3 ? 1 : banner.visibleId + 1;
|
||||
const next = banner.visibleId;
|
||||
|
||||
// Hide previous one
|
||||
const oldItem = document.getElementById(`banner-item-${prev}`);
|
||||
oldItem.classList.add("-left-full");
|
||||
oldItem.classList.remove("left-0");
|
||||
setTimeout(() => {
|
||||
oldItem.classList.remove("transition-all");
|
||||
}, transDuration + 10);
|
||||
setTimeout(() => {
|
||||
oldItem.classList.add("opacity-0");
|
||||
}, transDuration + 30);
|
||||
setTimeout(() => {
|
||||
oldItem.classList.remove("-left-full");
|
||||
oldItem.classList.add("left-full");
|
||||
}, transDuration * 2);
|
||||
|
||||
// Show new one
|
||||
const newItem = document.getElementById(`banner-item-${next}`);
|
||||
newItem.classList.remove("opacity-0");
|
||||
newItem.classList.add("transition-all");
|
||||
newItem.classList.add("left-0");
|
||||
newItem.classList.remove("left-full");
|
||||
}, nextDelay);
|
||||
|
||||
// Observe banner and set is visible or not to
|
||||
// Update float button and menu position
|
||||
let options = {
|
||||
root: null,
|
||||
rootMargin: "0px",
|
||||
threshold: 0.35,
|
||||
};
|
||||
|
||||
let observer = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) bannerStore.setBannerVisible(true);
|
||||
if (!entry.isIntersecting) bannerStore.setBannerVisible(false);
|
||||
});
|
||||
}, options);
|
||||
|
||||
observer.observe(document.getElementById("banner"));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupBanner();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="banner" tabindex="-1" role="list" class="banner-container">
|
||||
<div role="img" aria-hidden="true" class="banner-bg"></div>
|
||||
<div
|
||||
v-for="index in 3"
|
||||
role="listitem"
|
||||
:id="`banner-item-${index}`"
|
||||
class="banner-item"
|
||||
:class="[index === 1 ? 'left-0' : 'left-full opacity-0']"
|
||||
>
|
||||
<p class="banner-item-text">
|
||||
{{ $t(`dashboard_banner_title_${index}`) }}
|
||||
<a
|
||||
:tabindex="bannerIndex"
|
||||
class="banner-item-link"
|
||||
:href="$t(`dashboard_banner_link_${index}`)"
|
||||
>
|
||||
{{ $t(`dashboard_banner_link_text_${index}`) }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
67
vuejs/client/src/components/Dashboard/Footer.vue
Normal file
67
vuejs/client/src/components/Dashboard/Footer.vue
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive } from "vue";
|
||||
import { footerIndex } from "@utils/tabindex";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
|
||||
const items = [
|
||||
{
|
||||
href: "https://www.bunkerweb.io?utm_campaign=self&utm_source=ui",
|
||||
title: t("dashboard_bw"),
|
||||
},
|
||||
{
|
||||
href: "https://docs.bunkerweb.io?utm_campaign=self&utm_source=ui",
|
||||
title: t("dashboard_docs"),
|
||||
},
|
||||
{
|
||||
href: "https://www.bunkerweb.io/privacy-policy?utm_campaign=self&utm_source=ui",
|
||||
title: t("dashboard_privacy"),
|
||||
},
|
||||
{
|
||||
href: "https://www.bunkerity.com/fr/blog?utm_campaign=self&utm_source=ui",
|
||||
title: t("dashboard_blog"),
|
||||
},
|
||||
{
|
||||
href: "https://github.com/bunkerity/bunkerweb/blob/master/LICENSE",
|
||||
title: t("dashboard_license"),
|
||||
},
|
||||
{
|
||||
href: "/sitemap",
|
||||
title: t("dashboard_sitemap"),
|
||||
},
|
||||
];
|
||||
|
||||
const footer = reactive({
|
||||
date: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
footer.date = `Copyright © ${new Date().getFullYear()} Bunkerity`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer role="contentinfo" class="footer-container">
|
||||
<div class="footer-wrap">
|
||||
<div class="footer-items-container">
|
||||
<div class="footer-item-right-container">
|
||||
<div class="footer-item-right">
|
||||
{{ footer.date }}
|
||||
</div>
|
||||
</div>
|
||||
<ul class="footer-list-container">
|
||||
<li v-for="item in items">
|
||||
<a
|
||||
:tabindex="footerIndex"
|
||||
:href="item.href"
|
||||
class="footer-list-item"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
53
vuejs/client/src/components/Dashboard/Header.vue
Normal file
53
vuejs/client/src/components/Dashboard/Header.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<script setup>
|
||||
import { reactive, onMounted, computed } from "vue";
|
||||
|
||||
const header = reactive({
|
||||
splitPath: [],
|
||||
currPath: computed(() => {
|
||||
if (header.splitPath.length === 0) return "page";
|
||||
return header.splitPath[header.splitPath.length - 1];
|
||||
}),
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// Get current route and try to match with a menu item to highlight
|
||||
const pathName = window.location.pathname.toLowerCase();
|
||||
// Remove queries
|
||||
let splitPath = pathName.split("?")[0].split("/");
|
||||
if (splitPath.length === 0) return (header.splitPath = []);
|
||||
// Remove .html and index
|
||||
for (let i = 0; i < splitPath.length; i++) {
|
||||
const el = splitPath[i];
|
||||
splitPath[i] = el.replace(".html", "").replace("-", " ");
|
||||
}
|
||||
if (splitPath[splitPath.length - 1].includes("index")) splitPath.pop();
|
||||
splitPath = splitPath.filter((item) => item !== "");
|
||||
// We want to keep only last 2 params to avoid wide path
|
||||
for (let i = 0; splitPath.length > 2; i++) {
|
||||
splitPath.shift();
|
||||
}
|
||||
|
||||
header.splitPath = splitPath;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header-container">
|
||||
<header class="header-el">
|
||||
<div class="header-wrap">
|
||||
<nav>
|
||||
<!-- breadcrumb -->
|
||||
<h2 class="header-title">{{ header.currPath }}</h2>
|
||||
<ul class="header-breadcrumb-container">
|
||||
<li class="header-breadcrumb-item first">
|
||||
{{ $t("dashboard_bw") }}
|
||||
</li>
|
||||
<li class="header-breadcrumb-item slash mobile active">
|
||||
{{ header.splitPath[header.splitPath.length - 1] }}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
64
vuejs/client/src/components/Dashboard/LangSwitch.vue
Normal file
64
vuejs/client/src/components/Dashboard/LangSwitch.vue
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { langIndex } from "@utils/tabindex.js";
|
||||
const lang = reactive({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
function updateLangStorage(lang) {
|
||||
sessionStorage.setItem("lang", lang);
|
||||
document.location.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed bottom-0 left-1 z-[800]">
|
||||
<ul
|
||||
id="switch-lang"
|
||||
role="radiogroup"
|
||||
v-show="lang.isOpen"
|
||||
class="max-h-[300px] overflow-auto"
|
||||
>
|
||||
<li
|
||||
v-for="(locale, id) in $i18n.availableLocales"
|
||||
role="radio"
|
||||
:key="`locale-${locale}`"
|
||||
:aria-checked="$i18n.locale === locale ? 'true' : 'false'"
|
||||
>
|
||||
<button
|
||||
:tabindex="lang.isOpen ? langIndex : -1"
|
||||
@click="
|
||||
() => {
|
||||
lang.isOpen = false;
|
||||
updateLangStorage(locale);
|
||||
}
|
||||
"
|
||||
aria-controls="switch-lang-text"
|
||||
:aria-selected="$i18n.locale === locale ? 'true' : 'false'"
|
||||
>
|
||||
<span :id="`${locale}-${id}`" class="sr-only">{{ locale }}</span>
|
||||
<span
|
||||
:aria-labelledby="`${locale}-${id}`"
|
||||
:class="[`fi fi-${locale}`]"
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- current -->
|
||||
<button
|
||||
:tabindex="langIndex"
|
||||
aria-controls="switch-lang"
|
||||
:aria-expanded="lang.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('dashboard_lang_dropdown_button_desc')"
|
||||
@click="lang.isOpen = lang.isOpen ? false : true"
|
||||
>
|
||||
<span id="current-lang" class="sr-only">{{ $i18n.locale }}</span>
|
||||
<span
|
||||
:aria-labelledby="`current-lang`"
|
||||
id="switch-lang-text"
|
||||
:class="[`fi fi-${$i18n.locale}`]"
|
||||
></span>
|
||||
</button>
|
||||
<!-- end current -->
|
||||
</div>
|
||||
</template>
|
||||
32
vuejs/client/src/components/Dashboard/Layout.vue
Normal file
32
vuejs/client/src/components/Dashboard/Layout.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script setup>
|
||||
// Components
|
||||
import Footer from "@components/Footer.vue";
|
||||
import Loader from "@components/Loader.vue";
|
||||
import LangSwitch from "@components/LangSwitch.vue";
|
||||
import Menu from "@components/Menu.vue";
|
||||
import FeedbackStructure from "@components/Feedback/Structure.vue";
|
||||
import News from "@components/News.vue";
|
||||
import Header from "@components/Header.vue";
|
||||
import Refresh from "@components/Refresh.vue";
|
||||
import Banner from "@components/Banner.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<noscript class="no-script"
|
||||
>Your browser does not support JavaScript!</noscript
|
||||
>
|
||||
<Loader />
|
||||
<LangSwitch />
|
||||
<Banner />
|
||||
<!-- info -->
|
||||
<main role="main" class="content-container">
|
||||
<Menu />
|
||||
<News />
|
||||
<div class="content-wrap">
|
||||
<Header />
|
||||
<slot></slot>
|
||||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
<!-- end info -->
|
||||
</template>
|
||||
57
vuejs/client/src/components/Dashboard/Loader.vue
Normal file
57
vuejs/client/src/components/Dashboard/Loader.vue
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import logoMenu2 from "@public/images/logo-menu-2.png";
|
||||
|
||||
onMounted(() => {
|
||||
class Loader {
|
||||
constructor() {
|
||||
this.logoContainer = document.querySelector("[data-loader]");
|
||||
this.logoEl = document.querySelector("[data-loader-img]");
|
||||
this.isLoading = true;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.loading();
|
||||
setTimeout(() => {
|
||||
this.logoContainer.classList.add("opacity-0");
|
||||
}, 1350);
|
||||
|
||||
setTimeout(() => {
|
||||
this.isLoading = false;
|
||||
this.logoContainer.classList.add("hidden");
|
||||
}, 1650);
|
||||
}
|
||||
|
||||
loading() {
|
||||
if ((this.isLoading = true)) {
|
||||
setTimeout(() => {
|
||||
this.logoEl.classList.toggle("scale-105");
|
||||
this.loading();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setLoader = new Loader();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-labelledby="loader-text"
|
||||
data-loader
|
||||
class="loader-container"
|
||||
>
|
||||
<p id="loader-text" class="sr-only">{{ $t("dashboard_loader_label") }}</p>
|
||||
<img
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
data-loader-img
|
||||
:src="logoMenu2"
|
||||
class="loader-container-img"
|
||||
:alt="$t('dashboard_logo_alt')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
410
vuejs/client/src/components/Dashboard/Menu.vue
Normal file
410
vuejs/client/src/components/Dashboard/Menu.vue
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
<script setup>
|
||||
import MenuSvgHome from "@components/Menu/Svg/Home.vue";
|
||||
import MenuSvgInstances from "@components/Menu/Svg/Instances.vue";
|
||||
import MenuSvgGlobalConf from "@components/Menu/Svg/GlobalConf.vue";
|
||||
import MenuSvgConfigs from "@components/Menu/Svg/Configs.vue";
|
||||
import MenuSvgPlugins from "@components/Menu/Svg/Plugins.vue";
|
||||
import MenuSvgJobs from "@components/Menu/Svg/Jobs.vue";
|
||||
import MenuSvgTwitter from "@components/Menu/Svg/Twitter.vue";
|
||||
import MenuSvgLinkedin from "@components/Menu/Svg/Linkedin.vue";
|
||||
import MenuSvgDiscord from "@components/Menu/Svg/Discord.vue";
|
||||
import MenuSvgServices from "@components/Menu/Svg/Services.vue";
|
||||
import MenuSvgGithub from "@components/Menu/Svg/Github.vue";
|
||||
import MenuSvgBans from "@components/Menu/Svg/Bans.vue";
|
||||
import MenuSvgActions from "@components/Menu/Svg/Actions.vue";
|
||||
import MenuSvgReporting from "@components/Menu/Svg/Reporting.vue";
|
||||
import { reactive, onMounted } 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";
|
||||
|
||||
// Use to update position when banner is visible or not
|
||||
const bannerStore = useBannerStore();
|
||||
|
||||
// Navigation with components
|
||||
// resolveComponent allow to replace a tag by a real Vue component
|
||||
const navList = [
|
||||
{ tag: "home", svg: MenuSvgHome, path: "/home" },
|
||||
{
|
||||
tag: "instances",
|
||||
svg: MenuSvgInstances,
|
||||
path: "/instances",
|
||||
},
|
||||
|
||||
{
|
||||
tag: "global_config",
|
||||
svg: MenuSvgGlobalConf,
|
||||
path: "/global-config",
|
||||
},
|
||||
{
|
||||
tag: "services",
|
||||
svg: MenuSvgServices,
|
||||
path: "/services",
|
||||
},
|
||||
{
|
||||
tag: "configs",
|
||||
svg: MenuSvgConfigs,
|
||||
path: "/configs",
|
||||
},
|
||||
{
|
||||
tag: "plugins",
|
||||
svg: MenuSvgPlugins,
|
||||
path: "/plugins",
|
||||
},
|
||||
{ tag: "jobs", svg: MenuSvgJobs, path: "/jobs" },
|
||||
{ tag: "bans", svg: MenuSvgBans, path: "/bans" },
|
||||
{
|
||||
tag: "actions",
|
||||
svg: MenuSvgActions,
|
||||
path: "/actions",
|
||||
},
|
||||
{
|
||||
tag: "reporting",
|
||||
svg: MenuSvgReporting,
|
||||
path: "/reporting",
|
||||
},
|
||||
];
|
||||
|
||||
// Social links
|
||||
const socialList = [
|
||||
{
|
||||
tag: "twitter",
|
||||
href: "https://twitter.com/bunkerity",
|
||||
svg: MenuSvgTwitter,
|
||||
},
|
||||
{
|
||||
tag: "linkedin",
|
||||
href: "https://www.linkedin.com/company/bunkerity/",
|
||||
svg: MenuSvgLinkedin,
|
||||
},
|
||||
{
|
||||
tag: "discord",
|
||||
href: "https://discord.gg/fTf46FmtyD",
|
||||
svg: MenuSvgDiscord,
|
||||
},
|
||||
{
|
||||
tag: "github",
|
||||
href: "https://github.com/bunkerity",
|
||||
svg: MenuSvgGithub,
|
||||
},
|
||||
];
|
||||
|
||||
const menu = reactive({
|
||||
pagePlugins: [], // Render custom page plugins links
|
||||
darkMode: false, // Apply dark mode state
|
||||
isActive: false, // Handle menu display/expand
|
||||
isDesktop: true, // Expand logic exclude with desktop
|
||||
currPath: false,
|
||||
});
|
||||
|
||||
function getDarkMode() {
|
||||
let darkMode = false;
|
||||
// Case on storage
|
||||
if (sessionStorage.getItem("mode")) {
|
||||
darkMode = sessionStorage.getItem("mode") === "dark" ? true : false;
|
||||
} else if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
// dark mode
|
||||
darkMode = true;
|
||||
sessionStorage.setItem("mode", "dark");
|
||||
} else {
|
||||
darkMode = false;
|
||||
sessionStorage.setItem("mode", "light");
|
||||
}
|
||||
|
||||
return darkMode;
|
||||
}
|
||||
|
||||
function switchMode() {
|
||||
menu.darkMode = menu.darkMode ? false : true;
|
||||
sessionStorage.setItem("mode", menu.darkMode ? "dark" : "light");
|
||||
updateMode();
|
||||
}
|
||||
|
||||
function updateMode() {
|
||||
try {
|
||||
menu.darkMode
|
||||
? document.querySelector("html").classList.add("dark")
|
||||
: document.querySelector("html").classList.remove("dark");
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menu.isActive = false;
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
menu.isActive = menu.isActive ? false : true;
|
||||
}
|
||||
|
||||
// Check device desktop using breakpoint
|
||||
onMounted(() => {
|
||||
// setup darkmode
|
||||
menu.darkMode = getDarkMode();
|
||||
|
||||
// Get current route and try to match with a menu item to highlight
|
||||
const pathName = window.location.pathname.toLowerCase();
|
||||
navList.forEach((item) => {
|
||||
const title = item.tag.replaceAll(" ", "-").toLowerCase();
|
||||
if (pathName.includes(title)) menu.currPath = title;
|
||||
});
|
||||
|
||||
// Determine menu behavior (static or float)
|
||||
menu.isDesktop = window.innerWidth < 1200 ? false : true;
|
||||
window.addEventListener("resize", () => {
|
||||
menu.isDesktop = window.innerWidth < 1200 ? false : true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- float button-->
|
||||
<button
|
||||
aria-controls="sidebar-menu"
|
||||
:tabindex="menu.isDesktop ? '-1' : menuFloatIndex"
|
||||
:aria-expanded="menu.isDesktop ? 'true' : menu.isActive ? 'true' : 'false'"
|
||||
@click="toggleMenu()"
|
||||
:class="['menu-float-btn', bannerStore.bannerClass]"
|
||||
aria-describedby="sidebar-menu-toggle"
|
||||
>
|
||||
<span class="sr-only" id="sidebar-menu-toggle">
|
||||
{{ $t("dashboard_menu_toggle_sidebar") }}
|
||||
</span>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
fill="#0D6EFD"
|
||||
class="menu-float-btn-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- end float button-->
|
||||
|
||||
<!-- left sidebar -->
|
||||
<aside
|
||||
id="sidebar-menu"
|
||||
data-sidebar-menu
|
||||
:aria-hidden="menu.isDesktop ? 'false' : menu.isActive ? 'false' : 'true'"
|
||||
:class="[
|
||||
'menu-container xl:translate-x-0',
|
||||
bannerStore.bannerClass,
|
||||
menu.isDesktop ? true : menu.isActive ? '' : 'active',
|
||||
]"
|
||||
>
|
||||
<!-- close btn-->
|
||||
<button
|
||||
aria-controls="sidebar-menu"
|
||||
:aria-expanded="
|
||||
menu.isDesktop ? 'true' : menu.isActive ? 'true' : 'false'
|
||||
"
|
||||
:tabindex="menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'"
|
||||
@click="closeMenu()"
|
||||
class="menu-close-btn"
|
||||
:class="[menu.isDesktop ? 'hidden' : '']"
|
||||
aria-describedby="sidebar-menu-close"
|
||||
>
|
||||
<span class="sr-only" id="sidebar-menu-close">
|
||||
{{ $t("dashboard_menu_close_sidebar") }}
|
||||
</span>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
@click="closeMenu()"
|
||||
class="menu-close-btn-svg"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- close btn-->
|
||||
|
||||
<div class="menu-top-content">
|
||||
<!-- logo and version -->
|
||||
<div class="w-full">
|
||||
<a
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
aria-labelledby="logo-link-label"
|
||||
class="menu-logo-container"
|
||||
:href="menu.currPath === '/home' ? '#' : '/home'"
|
||||
>
|
||||
<span id="logo-link-label" class="sr-only">
|
||||
{{ $t("dashboard_logo_link_label") }}
|
||||
</span>
|
||||
<img :aria-hidden="'true'" :src="logoMenu2" class="menu-logo-dark" />
|
||||
<img :aria-hidden="'true'" :src="logoMenu" class="menu-logo-light" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-2 w-full px-1">
|
||||
<h1 class="menu-account-title">
|
||||
{{ username.substring(0, 10) }}
|
||||
</h1>
|
||||
<a class="menu-account-link" href="/account">manage account </a>
|
||||
</div>
|
||||
<hr class="menu-separator" />
|
||||
<!-- end logo version -->
|
||||
</div>
|
||||
|
||||
<div :class="[menu - nav - list - container, bannerStore.bannerClass]">
|
||||
<ul role="navigation" class="menu-nav-list">
|
||||
<!-- item -->
|
||||
<li v-for="(item, id) in navList" :key="id" class="mt-0.5 w-full">
|
||||
<a
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
:class="[
|
||||
item.path.toLowerCase().includes(menu.currPath) ? 'active' : '',
|
||||
'menu-nav-item-anchor',
|
||||
]"
|
||||
:href="
|
||||
item.path.toLowerCase().includes(menu.currPath) ? '#' : item.path
|
||||
"
|
||||
>
|
||||
<div class="menu-nav-item-container">
|
||||
<component :is="item.svg"></component>
|
||||
</div>
|
||||
<span class="menu-nav-item-title">
|
||||
{{ $t(`dashboard_${item.tag}`) }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- end item -->
|
||||
</ul>
|
||||
<!-- end default anchor -->
|
||||
|
||||
<!-- plugins -->
|
||||
<ul>
|
||||
<li class="menu-page-plugin-item-title">
|
||||
<p class="menu-page-plugin-title">
|
||||
{{ $t("dashboard_menu_plugins_title") }}
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
v-for="plugin in menu.pagePlugins"
|
||||
class="menu-page-plugin-item-page"
|
||||
>
|
||||
<a
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
target="_blank"
|
||||
class="menu-page-plugin-anchor"
|
||||
:href="`/plugins?plugin_id=${plugin.id}`"
|
||||
>
|
||||
<div aria-hidden="true" class="menu-page-plugin-svg-container">
|
||||
<svg
|
||||
v-if="plugin.type !== 'pro'"
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
class="fill-gray-500 h-5 w-5 relative"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 384 512"
|
||||
>
|
||||
<path
|
||||
d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="plugin.type === 'pro'"
|
||||
class="h-5 w-5 dark:brightness-90 relative"
|
||||
viewBox="0 0 48 46"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
class="fill-yellow-500"
|
||||
d="M43.218 28.2327L43.6765 23.971C43.921 21.6973 44.0825 20.1957 43.9557 19.2497L44 19.25C46.071 19.25 47.75 17.5711 47.75 15.5C47.75 13.4289 46.071 11.75 44 11.75C41.929 11.75 40.25 13.4289 40.25 15.5C40.25 16.4366 40.5935 17.2931 41.1613 17.9503C40.346 18.4535 39.2805 19.515 37.6763 21.1128C36.4405 22.3438 35.8225 22.9593 35.1333 23.0548C34.7513 23.1075 34.3622 23.0532 34.0095 22.898C33.373 22.6175 32.9485 21.8567 32.0997 20.335L27.6262 12.3135C27.1025 11.3747 26.6642 10.5889 26.2692 9.95662C27.89 9.12967 29 7.44445 29 5.5C29 2.73857 26.7615 0.5 24 0.5C21.2385 0.5 19 2.73857 19 5.5C19 7.44445 20.11 9.12967 21.7308 9.95662C21.3358 10.589 20.8975 11.3746 20.3738 12.3135L15.9002 20.335C15.0514 21.8567 14.627 22.6175 13.9905 22.898C13.6379 23.0532 13.2487 23.1075 12.8668 23.0548C12.1774 22.9593 11.5595 22.3438 10.3238 21.1128C8.71968 19.515 7.6539 18.4535 6.83882 17.9503C7.4066 17.2931 7.75 16.4366 7.75 15.5C7.75 13.4289 6.07107 11.75 4 11.75C1.92893 11.75 0.25 13.4289 0.25 15.5C0.25 17.5711 1.92893 19.25 4 19.25L4.04428 19.2497C3.91755 20.1957 4.07905 21.6973 4.32362 23.971L4.782 28.2327C5.03645 30.5982 5.24802 32.849 5.50717 34.875H42.4928C42.752 32.849 42.9635 30.5982 43.218 28.2327Z"
|
||||
fill="#1C274C"
|
||||
/>
|
||||
<path
|
||||
class="fill-yellow-500"
|
||||
d="M21.2803 45.5H26.7198C33.8098 45.5 37.3545 45.5 39.7198 43.383C40.7523 42.4588 41.4057 40.793 41.8775 38.625H6.1224C6.59413 40.793 7.24783 42.4588 8.2802 43.383C10.6454 45.5 14.1903 45.5 21.2803 45.5Z"
|
||||
fill="#1C274C"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="menu-page-plugin-name">{{ plugin.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- end plugins -->
|
||||
</div>
|
||||
|
||||
<!-- bottom sidebar -->
|
||||
<div class="menu-bottom-content">
|
||||
<hr class="line-separator" />
|
||||
|
||||
<!-- dark/light mode -->
|
||||
<div class="menu-mode-container">
|
||||
<input
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
:checked="menu.darkMode"
|
||||
@click="switchMode()"
|
||||
id="darkMode"
|
||||
data-dark-toggle
|
||||
class="menu-mode-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label for="darkMode" data-dark-toggle-label class="menu-mode-label">
|
||||
{{
|
||||
menu.darkMode
|
||||
? $t("dashboard_menu_mode_dark")
|
||||
: $t("dashboard_menu_mode_light")
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
<!-- end dark/light mode -->
|
||||
|
||||
<!-- social-->
|
||||
<ul class="menu-social-list">
|
||||
<li v-for="(item, id) in socialList" class="menu-social-list-item">
|
||||
<a
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
:href="item.href"
|
||||
target="_blank"
|
||||
:aria-labelledby="`${item}-id`"
|
||||
>
|
||||
<span :id="`${item}-id`" class="sr-only">
|
||||
{{ $t(`dashboard_menu_${item.tag}_label`) }}
|
||||
</span>
|
||||
<component :is="item.svg"></component>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- end social-->
|
||||
|
||||
<!-- logout-->
|
||||
<div class="w-full">
|
||||
<button
|
||||
:tabindex="
|
||||
menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'
|
||||
"
|
||||
@click="getlogout()"
|
||||
class="menu-logout"
|
||||
>
|
||||
{{ $t("dashboard_menu_log_out") }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- end logout-->
|
||||
</div>
|
||||
<!-- end bottom sidebar -->
|
||||
</aside>
|
||||
<!-- end left sidebar -->
|
||||
</template>
|
||||
183
vuejs/client/src/components/Dashboard/News.vue
Normal file
183
vuejs/client/src/components/Dashboard/News.vue
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive } from "vue";
|
||||
import { newsIndex } from "@utils/tabindex.js";
|
||||
import { useBannerStore } from "@store/global.js";
|
||||
// Use to update position when banner is visible or not
|
||||
const bannerStore = useBannerStore();
|
||||
|
||||
// DATA
|
||||
const news = reactive({
|
||||
isActive: false,
|
||||
posts: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
fetch("https://www.bunkerweb.io/api/posts/0/2")
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((res) => {
|
||||
news.posts = res.data;
|
||||
});
|
||||
} catch (err) {}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- float button-->
|
||||
<button
|
||||
:tabindex="newsIndex"
|
||||
aria-controls="sidebar-news"
|
||||
:aria-expanded="news.isActive ? 'true' : 'false'"
|
||||
@click="news.isActive = news.isActive ? false : true"
|
||||
:class="['news-float-btn', bannerStore.bannerClass]"
|
||||
class="news-float-btn"
|
||||
aria-describedby="sidebar-news-toggle"
|
||||
>
|
||||
<span class="sr-only" id="sidebar-news-toggle">
|
||||
{{ $t("dashboard_news_toggle_sidebar") }}
|
||||
</span>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
class="news-float-btn-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M96 96c0-35.3 28.7-64 64-64H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H80c-44.2 0-80-35.8-80-80V128c0-17.7 14.3-32 32-32s32 14.3 32 32V400c0 8.8 7.2 16 16 16s16-7.2 16-16V96zm64 24v80c0 13.3 10.7 24 24 24H424c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24H184c-13.3 0-24 10.7-24 24zm0 184c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zm160 0c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16s-7.2-16-16-16H336c-8.8 0-16 7.2-16 16zM160 400c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zm160 0c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16s-7.2-16-16-16H336c-8.8 0-16 7.2-16 16z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- end float button-->
|
||||
|
||||
<!-- right sidebar -->
|
||||
<aside
|
||||
:aria-hidden="news.isActive ? 'false' : 'true'"
|
||||
id="sidebar-news"
|
||||
:class="[news.isActive ? '' : 'translate-x-[22.5rem]', 'news-sidebar']"
|
||||
>
|
||||
<!-- close btn-->
|
||||
<button
|
||||
:tabindex="news.isActive ? newsIndex : '-1'"
|
||||
class="news-close-btn"
|
||||
aria-controls="sidebar-news"
|
||||
:aria-expanded="news.isActive ? 'true' : 'false'"
|
||||
@click="news.isActive = false"
|
||||
aria-describedby="sidebar-news-close"
|
||||
>
|
||||
<span class="sr-only" id="sidebar-news-close">
|
||||
{{ $t("dashboard_news_close_sidebar") }}
|
||||
</span>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
@click="news.isActive = false"
|
||||
class="news-close-btn-svg"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- close btn-->
|
||||
|
||||
<!-- header -->
|
||||
<div class="news-sidebar-header">
|
||||
<div class="float-left">
|
||||
<h5 class="news-sidebar-title">{{ $t("dashboard_news_title") }}</h5>
|
||||
<p class="news-sidebar-subtitle">{{ $t("dashboard_news_subtitle") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="line-separator" />
|
||||
<!-- end header -->
|
||||
{{ news.posts }}
|
||||
<!-- news-->
|
||||
<div class="flex-auto overflow-auto">
|
||||
<p v-if="news.posts.length === 0" class="news-sidebar-no-posts-content">
|
||||
{{ $t("dashboard_news_fetch_error") }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- end news-->
|
||||
|
||||
<!-- newsletter -->
|
||||
<hr class="line-separator" />
|
||||
|
||||
<form
|
||||
action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
|
||||
method="POST"
|
||||
class="news-newsletter-form"
|
||||
id="subscribe-newsletter"
|
||||
>
|
||||
<h5 class="news-newsletter-title">
|
||||
{{ $t("dashboard_newsletter_title") }}
|
||||
</h5>
|
||||
<div class="flex">
|
||||
<input
|
||||
:tabindex="news.isActive ? newsIndex : '-1'"
|
||||
type="text"
|
||||
id="newsletter-email"
|
||||
name="EMAIL"
|
||||
class="news-newsletter-input"
|
||||
:placeholder="$t('dashboard_newsletter_placeholder')"
|
||||
pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}$"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-2 mb-4">
|
||||
<div class="relative">
|
||||
<div
|
||||
data-checkbox-handler="newsletter-check"
|
||||
class="relative mb-7 md:mb-0"
|
||||
>
|
||||
<input
|
||||
:tabindex="news.isActive ? newsIndex : '-1'"
|
||||
id="newsletter-check"
|
||||
class="news-newsletter-checkbox"
|
||||
type="checkbox"
|
||||
data-pattern="^(yes|no)$"
|
||||
value="no"
|
||||
required
|
||||
/>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
data-checkbox-handler="newsletter-check"
|
||||
class="news-newsletter-checkbox-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
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>
|
||||
</div>
|
||||
<label class="news-newsletter-checkbox-content" for="newsletter-check">
|
||||
{{ $t("dashboard_newsletter_privacy_text") }}
|
||||
<a
|
||||
:tabindex="news.isActive ? newsIndex : '-1'"
|
||||
class="italic"
|
||||
href="https://www.bunkerity.com/privacy-policy?utm_campaign=self&utm_source=ui"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("dashboard_newsletter_privacy_text_link") }}
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
:tabindex="news.isActive ? newsIndex : '-1'"
|
||||
type="submit"
|
||||
formtarget="_blank"
|
||||
class="news-newsletter-confirm-btn"
|
||||
>
|
||||
{{ $t("dashboard_newsletter_subscribe_button") }}
|
||||
</button>
|
||||
</form>
|
||||
<!-- end newsletter -->
|
||||
</aside>
|
||||
<!-- end right sidebar -->
|
||||
</template>
|
||||
8
vuejs/client/src/pages/dashboard/Dashboard.vue
Normal file
8
vuejs/client/src/pages/dashboard/Dashboard.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout></DashboardLayout>
|
||||
</template>
|
||||
11
vuejs/client/src/pages/dashboard/dashboard.js
Normal file
11
vuejs/client/src/pages/dashboard/dashboard.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { getI18n } from "@utils/lang.js";
|
||||
import Dashboard from "./Dashboard.vue";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(Dashboard)
|
||||
.use(pinia)
|
||||
.use(getI18n(["dashboard", "api", "action", "bans", "inp"]))
|
||||
.mount("#app");
|
||||
15
vuejs/client/src/pages/dashboard/index.html
Normal file
15
vuejs/client/src/pages/dashboard/index.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/images/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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
vuejs/client/src/store/global.js
Normal file
20
vuejs/client/src/store/global.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useBannerStore = defineStore("banner", () => {
|
||||
const isBanner = ref(true);
|
||||
const bannerClass = ref("banner");
|
||||
|
||||
async function setBannerVisible(bool) {
|
||||
isBanner.value = bool;
|
||||
bannerClass.value = bool ? "banner" : "no-banner";
|
||||
}
|
||||
|
||||
return { isBanner, bannerClass, setBannerVisible };
|
||||
});
|
||||
|
||||
export const useBackdropStore = defineStore("backdrop", () => {
|
||||
const clickCount = ref(0);
|
||||
|
||||
return { clickCount };
|
||||
});
|
||||
Loading…
Reference in a new issue