mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-07-05 10:17:20 +00:00
feat: vite dashboard tweaks/fixes
This commit is contained in:
parent
aaac328138
commit
177f13d1fc
16 changed files with 94 additions and 128 deletions
|
@ -15,8 +15,9 @@
|
|||
The Zeppelin website requires JavaScript to load.
|
||||
</noscript>
|
||||
|
||||
<script type="text/javascript" src="/env.js"></script>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
|
||||
<script type="text/javascript" src="/env.js"></script>
|
||||
<script type="module" src="./src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,20 +2,26 @@ import Fastify from "fastify";
|
|||
import fastifyStatic from "@fastify/static";
|
||||
import path from "node:path";
|
||||
|
||||
const fastify = Fastify({ logger: true });
|
||||
const fastify = Fastify({
|
||||
// We already get logs from nginx, so disable here
|
||||
logger: false,
|
||||
});
|
||||
|
||||
fastify.get("/env.js", (req, reply) => {
|
||||
reply.header("Content-Type", "application/javascript; charset=utf8");
|
||||
reply.send(`window.API_URL = ${JSON.stringify(process.env.API_URL)}`);
|
||||
fastify.addHook("preHandler", (req, reply, done) => {
|
||||
if (req.url === "/env.js") {
|
||||
reply.header("Content-Type", "application/javascript; charset=utf8");
|
||||
reply.send(`window.API_URL = ${JSON.stringify(process.env.API_URL)};`);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
fastify.register(fastifyStatic, {
|
||||
root: path.join(__dirname, "dist"),
|
||||
root: path.join(import.meta.dirname, "dist"),
|
||||
wildcard: false,
|
||||
});
|
||||
|
||||
fastify.get("*", (req, reply) => {
|
||||
reply.header("Content-Type", "text/html; charset=utf8").send(indexContent);
|
||||
reply.sendFile("index.html");
|
||||
});
|
||||
|
||||
fastify.listen({ port: 3002, host: '0.0.0.0' }, (err, address) => {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { RootStore } from "./store";
|
||||
const apiUrl = window.API_URL;
|
||||
|
||||
type QueryParamObject = { [key: string]: string | null };
|
||||
|
||||
|
@ -28,7 +27,7 @@ function buildQueryString(params: QueryParamObject) {
|
|||
}
|
||||
|
||||
export function request(resource, fetchOpts: RequestInit = {}) {
|
||||
return fetch(`${apiUrl}/${resource}`, fetchOpts).then(async (res) => {
|
||||
return fetch(`${window.API_URL}/${resource}`, fetchOpts).then(async (res) => {
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
RootStore.dispatch("auth/expiredLogin");
|
||||
|
@ -74,7 +73,7 @@ type FormPostOpts = {
|
|||
export function formPost(resource: string, body: Record<any, any> = {}, opts: FormPostOpts = {}) {
|
||||
body["X-Api-Key"] = RootStore.state.auth.apiKey;
|
||||
const form = document.createElement("form");
|
||||
form.action = `${apiUrl}/${resource}`;
|
||||
form.action = `${window.API_URL}/${resource}`;
|
||||
form.method = "POST";
|
||||
form.enctype = "multipart/form-data";
|
||||
if (opts.target != null) {
|
||||
|
|
|
@ -11,7 +11,7 @@ const isAuthenticated = async () => {
|
|||
|
||||
export const authGuard: NavigationGuard = async (to, from, next) => {
|
||||
if (await isAuthenticated()) return next();
|
||||
window.location.href = `${process.env.API_URL}/auth/login`;
|
||||
window.location.href = `${window.API_URL}/auth/login`;
|
||||
};
|
||||
|
||||
export const loginCallbackGuard: NavigationGuard = async (to, from, next) => {
|
||||
|
@ -26,6 +26,6 @@ export const loginCallbackGuard: NavigationGuard = async (to, from, next) => {
|
|||
|
||||
export const authRedirectGuard: NavigationGuard = async (to, form, next) => {
|
||||
if (await isAuthenticated()) return next("/dashboard");
|
||||
window.location.href = `${process.env.API_URL}/auth/login`;
|
||||
window.location.href = `${window.API_URL}/auth/login`;
|
||||
return next();
|
||||
};
|
||||
|
|
53
dashboard/src/components/Splash.vue
Normal file
53
dashboard/src/components/Splash.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div class="splash">
|
||||
<div class="error" v-if="error">
|
||||
<div class="message">{{ error }}</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="logo-column">
|
||||
<img class="logo" src="/img/logo.png" alt="Zeppelin Logo" />
|
||||
</div>
|
||||
<div class="info-column">
|
||||
<h1>Zeppelin</h1>
|
||||
<div class="description">
|
||||
Zeppelin is a private moderation bot for Discord, designed with large servers and reliability in mind.
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn" href="/dashboard">Dashboard</a>
|
||||
<a class="btn" href="/docs">Documentation</a>
|
||||
</div>
|
||||
<ul class="links">
|
||||
<li>
|
||||
<a href="https://discord.gg/zeppelin">Official Discord Server</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Dragory/ZeppelinBot">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const errorMessages = {
|
||||
noAccess: "No dashboard access. If you think this is a mistake, please contact your server owner.",
|
||||
expiredLogin: "Dashboard login expired. Please log in again.",
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
watch(
|
||||
() => route.query.error,
|
||||
(value) => {
|
||||
error.value = errorMessages[String(value)] || null;
|
||||
},
|
||||
);
|
||||
</script>
|
|
@ -2,8 +2,8 @@ import "./style/app.css";
|
|||
|
||||
import { createApp } from "vue";
|
||||
|
||||
import VueHighlightJS from "vue3-highlightjs";
|
||||
import "highlight.js/styles/base16/ocean.css";
|
||||
import VueHighlightJS from "vue3-highlightjs";
|
||||
|
||||
import { router } from "./routes";
|
||||
import { RootStore } from "./store";
|
||||
|
@ -13,24 +13,14 @@ import "./directives/trim-indents";
|
|||
import App from "./components/App.vue";
|
||||
import { trimIndents } from "./directives/trim-indents";
|
||||
|
||||
if (!window.API_URL) {
|
||||
throw new Error("Missing API_URL");
|
||||
}
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
app.use(RootStore);
|
||||
|
||||
// Set up a read-only global variable to access specific env vars
|
||||
app.mixin({
|
||||
data() {
|
||||
return {
|
||||
get env() {
|
||||
return Object.freeze({
|
||||
API_URL: process.env.API_URL,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
app.use(VueHighlightJS);
|
||||
|
||||
app.directive("trim-indents", trimIndents);
|
|
@ -1,33 +0,0 @@
|
|||
import "./style/initial.css";
|
||||
import splashHtml from "./splash.html";
|
||||
|
||||
if (window.location.pathname !== "/") {
|
||||
import("./init-vue");
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector("#app").innerHTML = splashHtml;
|
||||
|
||||
const queryParams: any = window.location.search
|
||||
.slice(1)
|
||||
.split("&")
|
||||
.reduce((map, str) => {
|
||||
const pair = str.split("=");
|
||||
map[pair[0]] = pair[1];
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
if (queryParams.error) {
|
||||
const errorElement = document.querySelector("#error") as HTMLElement;
|
||||
errorElement.classList.add("has-error");
|
||||
|
||||
const errorMessages = {
|
||||
noAccess: "No dashboard access. If you think this is a mistake, please contact your server owner.",
|
||||
expiredLogin: "Dashboard login expired. Please log in again.",
|
||||
};
|
||||
|
||||
const errorMessageElem = document.createElement("div");
|
||||
errorMessageElem.classList.add("message");
|
||||
errorMessageElem.innerText = errorMessages[queryParams.error] || "Unexpected error";
|
||||
errorElement.appendChild(errorMessageElem);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import { authGuard, authRedirectGuard, loginCallbackGuard } from "./auth";
|
||||
import Splash from "./components/Splash.vue";
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: "/", component: Splash },
|
||||
|
||||
{ path: "/login", components: {}, beforeEnter: authRedirectGuard },
|
||||
{ path: "/login-callback", component: {}, beforeEnter: loginCallbackGuard },
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<div class="splash">
|
||||
<div id="error"></div>
|
||||
<div class="wrapper">
|
||||
<div class="logo-column">
|
||||
<img class="logo" src="/img/logo.png" alt="Zeppelin Logo" />
|
||||
</div>
|
||||
<div class="info-column">
|
||||
<h1>Zeppelin</h1>
|
||||
<div class="description">
|
||||
Zeppelin is a private moderation bot for Discord, designed with large servers and reliability in mind.
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn" href="/dashboard">Dashboard</a>
|
||||
<a class="btn" href="/docs">Documentation</a>
|
||||
</div>
|
||||
<ul class="links">
|
||||
<li>
|
||||
<a href="https://github.com/Dragory/ZeppelinBot">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.com/invite/w8njuNu">Discord</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.patreon.com/zeppelinbot">Patreon</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,9 +1,11 @@
|
|||
@import "tailwindcss";
|
||||
@import "./reset.css";
|
||||
@import "./base.css";
|
||||
@import "./splash.css";
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "vue-material-design-icons/styles.css";
|
||||
|
||||
@import "./content.css";
|
||||
|
||||
@import "./docs.css";
|
||||
|
||||
/* Reset some icon default styles for more predictable alignment */
|
||||
|
|
|
@ -66,15 +66,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
@media (width >= theme(--breakpoint-lg)) {
|
||||
.main-content {
|
||||
& h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@screen xl {
|
||||
@media (width >= theme(--breakpoint-xl)) {
|
||||
.main-content {
|
||||
& a:not([class]),
|
||||
& a[class=""] {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
@import "./reset.css";
|
||||
@import "./base.css";
|
||||
@import "./splash.css";
|
|
@ -15,17 +15,14 @@
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
& > #error {
|
||||
& > .error {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 750px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
|
||||
&.has-error {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
& .message {
|
||||
flex: 0 1 auto;
|
||||
text-align: left;
|
||||
|
@ -154,7 +151,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
|
||||
}
|
||||
|
|
4
dashboard/src/vite-env.d.ts
vendored
4
dashboard/src/vite-env.d.ts
vendored
|
@ -4,3 +4,7 @@ declare module '*.html' {
|
|||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
API_URL: string;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
import { defineConfig, Plugin } from "vite";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import tailwind from "@tailwindcss/vite";
|
||||
|
||||
function htmlImport(): Plugin {
|
||||
return {
|
||||
name: "html-import",
|
||||
transform(code, id) {
|
||||
if (id.endsWith(".html")) {
|
||||
return {
|
||||
code: `export default ${JSON.stringify(code)};`,
|
||||
map: null,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig((configEnv) => {
|
||||
return {
|
||||
server: {
|
||||
|
@ -34,7 +19,6 @@ export default defineConfig((configEnv) => {
|
|||
},
|
||||
}),
|
||||
tailwind(),
|
||||
htmlImport(),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue