mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-07-05 18:27:18 +00:00
Merge branch 'dashboard-vite'
This commit is contained in:
commit
f4d728ea25
47 changed files with 1345 additions and 9763 deletions
|
@ -5,5 +5,12 @@
|
|||
|
||||
"service": "devenv",
|
||||
"remoteUser": "ubuntu",
|
||||
"workspaceFolder": "/workspace/zeppelin"
|
||||
"workspaceFolder": "/workspace/zeppelin",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"Vue.volar"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
"cross-env": "^7.0.3",
|
||||
"deep-diff": "^1.0.2",
|
||||
"discord.js": "^14.19.3",
|
||||
"dotenv": "^4.0.0",
|
||||
"emoji-regex": "^8.0.0",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"express": "^4.20.0",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
collapseWhitespace: false,
|
||||
};
|
|
@ -14,7 +14,10 @@
|
|||
<h1>Zeppelin</h1>
|
||||
The Zeppelin website requires JavaScript to load.
|
||||
</noscript>
|
||||
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="text/javascript" src="/env.js"></script>
|
||||
<script type="module" src="./src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -3,54 +3,37 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rimraf dist && cross-env NODE_ENV=production webpack --config webpack.config.js",
|
||||
"build-debug": "rimraf dist && cross-env NODE_ENV=development webpack --config webpack.config.js",
|
||||
"watch": "cross-env NODE_ENV=development webpack-dev-server"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"babel-loader": "^9.1.2",
|
||||
"@tailwindcss/vite": "^4.1.8",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.8.1",
|
||||
"cssnano": "^4.1.10",
|
||||
"dotenv": "^16.4.5",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"postcss-nesting": "^11.3.0",
|
||||
"postcss-preset-env": "^8.5.1",
|
||||
"source-map-loader": "^4.0.1",
|
||||
"tailwindcss": "^1.9.6",
|
||||
"ts-loader": "^9.4.3",
|
||||
"vue-loader": "^17.4.2",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-merge": "^5.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^7.0.1",
|
||||
"fastify": "^4.26.2",
|
||||
"highlight.js": "^11.8.0",
|
||||
"humanize-duration": "^3.27.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"marked": "^5.1.0",
|
||||
"modern-css-reset": "^1.4.0",
|
||||
"moment": "^2.29.4",
|
||||
"postcss-nesting": "^13.0.1",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"vite": "npm:rolldown-vite@latest",
|
||||
"vue": "^3.5.13",
|
||||
"vue-material-design-icons": "^5.3.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-tsc": "^2.2.10",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
"vue3-highlightjs": "^1.0.5",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^7.0.1",
|
||||
"fastify": "^4.26.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
]
|
||||
|
|
8
dashboard/postcss.config.js
Normal file
8
dashboard/postcss.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import nesting from "postcss-nesting";
|
||||
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: [nesting]
|
||||
}
|
||||
|
||||
export default config;
|
2
dashboard/public/env.js
Normal file
2
dashboard/public/env.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Don't edit this directly, it uses env vars in prod via serve.js
|
||||
window.API_URL = "/api";
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -1,9 +1,22 @@
|
|||
const fastify = require("fastify")({ logger: true });
|
||||
const fastifyStatic = require("@fastify/static");
|
||||
const path = require("path");
|
||||
import Fastify from "fastify";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import path from "node:path";
|
||||
|
||||
const fastify = Fastify({
|
||||
// We already get logs from nginx, so disable here
|
||||
logger: false,
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { RootStore } from "./store";
|
||||
const apiUrl = process.env.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();
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import "../style/components.pcss";
|
||||
@reference "../style/app.css";
|
||||
|
||||
.expandable {
|
||||
--animation-time: 400ms;
|
||||
|
|
|
@ -87,6 +87,6 @@
|
|||
</template>
|
||||
|
||||
<script type="ts">
|
||||
import "../style/privacy-policy.pcss";
|
||||
import "../style/privacy-policy.css";
|
||||
export default {};
|
||||
</script>
|
||||
|
|
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>
|
|
@ -5,6 +5,8 @@
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "../style/app.css";
|
||||
|
||||
li {
|
||||
padding-bottom: 1px;
|
||||
|
||||
|
@ -26,7 +28,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.active a {
|
||||
.active :deep(a) {
|
||||
@apply text-gray-200;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
lang="yaml"
|
||||
theme="tomorrow_night"
|
||||
ref="aceEditor"
|
||||
v-options="{
|
||||
:options="{
|
||||
useSoftTabs: true,
|
||||
tabSize: 2
|
||||
}"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<h1>Guild Info</h1>
|
||||
<p>
|
||||
<img class="inline-block w-16 mr-4" style="vertical-align: -20px" src="../../img/squint.png"> What are you doing here
|
||||
<img class="inline-block w-16 mr-4" style="vertical-align: -20px" src="/img/squint.png"> What are you doing here
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<nav class="flex items-stretch flex-wrap pl-4 pr-2 py-1 border border-gray-700 rounded bg-gray-800 shadow-xl mb-8">
|
||||
<div class="flex-full md:flex-initial flex items-center">
|
||||
<img class="w-10 mr-5" :src="logoUrl" alt="" aria-hidden="true">
|
||||
<img class="w-10 mr-5" src="/img/logo.png" alt="" aria-hidden="true">
|
||||
|
||||
<router-link to="/dashboard">
|
||||
<h1 class="font-semibold">Zeppelin Dashboard</h1>
|
||||
|
@ -48,7 +48,6 @@
|
|||
|
||||
<script>
|
||||
import Title from "../Title.vue";
|
||||
import logoUrl from "../../img/logo.png";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -60,8 +59,5 @@
|
|||
window.location.pathname = '/';
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { logoUrl };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -104,8 +104,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import CodeBlock from "./CodeBlock";
|
||||
import Expandable from "../Expandable";
|
||||
import CodeBlock from "./CodeBlock.vue";
|
||||
import Expandable from "../Expandable.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -346,8 +346,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import CodeBlock from "./CodeBlock";
|
||||
import Expandable from "../Expandable";
|
||||
import CodeBlock from "./CodeBlock.vue";
|
||||
import Expandable from "../Expandable.vue";
|
||||
|
||||
export default {
|
||||
components: { CodeBlock, Expandable },
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Top bar -->
|
||||
<nav class="flex items-stretch pl-4 pr-2 py-1 border border-gray-700 rounded bg-gray-800 shadow-xl">
|
||||
<div class="flex-initial flex items-center">
|
||||
<img class="flex-auto w-10 mr-5" :src="logoUrl" alt="" aria-hidden="true">
|
||||
<img class="flex-auto w-10 mr-5" src="/img/logo.png" alt="" aria-hidden="true">
|
||||
|
||||
<router-link to="/docs">
|
||||
<h1 class="flex-auto font-semibold">Zeppelin Documentation</h1>
|
||||
|
@ -27,9 +27,9 @@
|
|||
<a class="sr-only-when-not-focused text-center block py-2" href="#main-anchor">Skip to main content</a>
|
||||
|
||||
<!-- Content wrapper -->
|
||||
<div class="flex flex-wrap items-start mt-8">
|
||||
<div class="flex flex-wrap lg:flex-nowrap items-start mt-8 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<nav class="docs-sidebar px-4 pt-2 pb-3 mr-8 mb-4 border border-gray-700 rounded bg-gray-800 shadow-md flex-full lg:flex-none lg:block" v-bind:class="{ closed: !mobileMenuOpen }">
|
||||
<nav class="docs-sidebar px-4 pt-2 pb-3 border border-gray-700 rounded bg-gray-800 shadow-md flex-full lg:flex-none lg:block" v-bind:class="{ closed: !mobileMenuOpen }">
|
||||
<div role="none" v-for="(group, index) in menu">
|
||||
<h1 class="font-bold" :aria-owns="'menu-group-' + index" :class="{'mt-4': typeof index === 'number' && index !== 0}">{{ group.label }}</h1>
|
||||
<ul v-bind:id="'menu-group-' + index" role="group" class="list-none pl-2">
|
||||
|
@ -41,7 +41,7 @@
|
|||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="docs-content main-content flex-flexible overflow-x-hidden">
|
||||
<main class="docs-content main-content flex-auto overflow-x-hidden">
|
||||
<a id="main-anchor" ref="main-anchor" tabindex="-1" class="sr-only"></a>
|
||||
<router-view :key="$route.fullPath"></router-view>
|
||||
</main>
|
||||
|
@ -53,7 +53,6 @@
|
|||
import {mapState} from "vuex";
|
||||
import Menu from 'vue-material-design-icons/Menu.vue';
|
||||
import Title from "../Title.vue";
|
||||
import logoUrl from "../../img/logo.png";
|
||||
|
||||
type TMenuItem = {
|
||||
to: string;
|
||||
|
@ -132,12 +131,12 @@
|
|||
data() {
|
||||
return {
|
||||
mobileMenuOpen: false,
|
||||
logoUrl,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleMobileMenu() {
|
||||
console.log('hi');
|
||||
this.mobileMenuOpen = !this.mobileMenuOpen;
|
||||
},
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import CodeBlock from "./CodeBlock";
|
||||
import CodeBlock from "./CodeBlock.vue";
|
||||
|
||||
export default {
|
||||
components: { CodeBlock },
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<MarkdownBlock :content="data.info.description" class="content"></MarkdownBlock>
|
||||
|
||||
<div v-if="data.info.type === 'legacy'">
|
||||
<div class="px-3 py-2 mb-4 rounded bg-gray-800 shadow-md inline-block flex">
|
||||
<div class="px-3 py-2 mb-4 rounded bg-gray-800 shadow-md flex">
|
||||
<div class="flex-none mr-2">
|
||||
<alert class="inline-icon mr-1 text-yellow-300" />
|
||||
</div>
|
||||
|
@ -163,6 +163,8 @@
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "../../style/app.css";
|
||||
|
||||
.command.target {
|
||||
@apply mt-5 mb-3;
|
||||
@apply pt-2 pb-2 pl-4 pr-4;
|
||||
|
@ -174,7 +176,6 @@
|
|||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import {mapState} from "vuex";
|
||||
import yaml from "js-yaml";
|
||||
import CodeBlock from "./CodeBlock.vue";
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import "./style/app.pcss";
|
||||
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.pcss";
|
||||
const splashHtml = require("./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,13 +1,12 @@
|
|||
@import "~tailwindcss/base.css";
|
||||
@import "~tailwindcss/components.css";
|
||||
@import "~tailwindcss/utilities.css";
|
||||
@import "./reset.css";
|
||||
@import "./base.css";
|
||||
@import "./splash.css";
|
||||
|
||||
@import "~vue-material-design-icons/styles.css";
|
||||
@import "tailwindcss";
|
||||
@import "vue-material-design-icons/styles.css";
|
||||
|
||||
@import "components.pcss";
|
||||
@import "content.pcss";
|
||||
|
||||
@import "docs.pcss";
|
||||
@import "./content.css";
|
||||
@import "./docs.css";
|
||||
|
||||
/* Reset some icon default styles for more predictable alignment */
|
||||
.material-design-icon > .material-design-icon__svg {
|
|
@ -1,3 +1,5 @@
|
|||
@import "./components.css";
|
||||
|
||||
.main-content {
|
||||
& h1 {
|
||||
@apply text-3xl;
|
||||
|
@ -25,7 +27,12 @@
|
|||
|
||||
& a:not([class]),
|
||||
& a[class=""] {
|
||||
@apply link;
|
||||
@apply text-blue-400;
|
||||
@apply underline;
|
||||
|
||||
&:hover {
|
||||
@apply text-blue-200;
|
||||
}
|
||||
}
|
||||
|
||||
& ul:not([class]) {
|
||||
|
@ -47,7 +54,11 @@
|
|||
}
|
||||
|
||||
& code:not([class]) {
|
||||
@apply inline-code;
|
||||
@apply inline-block;
|
||||
@apply bg-gray-800;
|
||||
@apply px-1;
|
||||
@apply rounded;
|
||||
@apply text-sm;
|
||||
}
|
||||
|
||||
& .expandable:not(.wide) {
|
||||
|
@ -55,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=""] {
|
|
@ -4,7 +4,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@screen until-lg {
|
||||
@media (width < theme(--breakpoint-lg)) {
|
||||
.docs-sidebar.closed:not(:focus-within) {
|
||||
@apply sr-only;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
@import "./reset.pcss";
|
||||
@import "./base.pcss";
|
||||
@import "./splash.pcss";
|
50
dashboard/src/style/reset.css
Normal file
50
dashboard/src/style/reset.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
@layer base {
|
||||
/* Box sizing rules */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Remove default padding */
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Remove default margin */
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
li,
|
||||
figure,
|
||||
figcaption,
|
||||
blockquote,
|
||||
dl,
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Inherit fonts for inputs and buttons */
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* Remove all animations and transitions for people that prefer not to see them */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/* Box sizing rules */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Remove default padding */
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Remove default margin */
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
li,
|
||||
figure,
|
||||
figcaption,
|
||||
blockquote,
|
||||
dl,
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Inherit fonts for inputs and buttons */
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* Remove all animations and transitions for people that prefer not to see them */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
10
dashboard/src/vite-env.d.ts
vendored
Normal file
10
dashboard/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.html' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
API_URL: string;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
module.exports = {
|
||||
important: true,
|
||||
theme: {
|
||||
extend: {
|
||||
lineHeight: {
|
||||
zero: "0",
|
||||
},
|
||||
flex: {
|
||||
full: "0 0 100%",
|
||||
flexible: "1 1 0",
|
||||
},
|
||||
},
|
||||
screens: {
|
||||
sm: "640px",
|
||||
md: "768px",
|
||||
"until-lg": { max: "1023px" },
|
||||
lg: "1024px",
|
||||
xl: "1280px",
|
||||
"2xl": "1536px",
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
purge: ["./src/**/*.{html,vue}"],
|
||||
future: {
|
||||
purgeLayersByDefault: true,
|
||||
},
|
||||
};
|
10
dashboard/ts-vue-shim.d.ts
vendored
10
dashboard/ts-vue-shim.d.ts
vendored
|
@ -1,10 +0,0 @@
|
|||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
const component: DefineComponent;
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
"esModuleInterop": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.vue", "./ts-vue-shim.d.ts"],
|
||||
"include": ["src/**/*.ts", "src/**/*.vue"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../shared/tsconfig.json"
|
||||
|
|
24
dashboard/vite.config.ts
Normal file
24
dashboard/vite.config.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import tailwind from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig((configEnv) => {
|
||||
return {
|
||||
server: {
|
||||
port: 3002,
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
},
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
// Needed to prevent hardcoded code blocks from breaking in docs
|
||||
whitespace: "preserve",
|
||||
},
|
||||
},
|
||||
}),
|
||||
tailwind(),
|
||||
],
|
||||
};
|
||||
});
|
|
@ -1,196 +0,0 @@
|
|||
const path = require("path");
|
||||
const { VueLoaderPlugin } = require("vue-loader");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { merge } = require("webpack-merge");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require("dotenv");
|
||||
|
||||
dotenv.config({ path: path.resolve(process.cwd(), "../.env") });
|
||||
|
||||
const targetDir = path.normalize(path.join(__dirname, "dist"));
|
||||
|
||||
if (!process.env.NODE_ENV) {
|
||||
console.error("Please set NODE_ENV");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.env.API_URL) {
|
||||
console.error("API_URL missing from environment variables");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const babelOpts = {
|
||||
presets: ["@babel/preset-env"],
|
||||
};
|
||||
|
||||
const tsconfig = require("./tsconfig.json");
|
||||
const pathAliases = Object.entries(tsconfig.compilerOptions.paths || []).reduce((aliases, pair) => {
|
||||
let alias = pair[0];
|
||||
if (alias.endsWith("/*")) alias = alias.slice(0, -2);
|
||||
|
||||
let aliasPath = pair[1][0];
|
||||
if (aliasPath.endsWith("/*")) aliasPath = aliasPath.slice(0, -2);
|
||||
|
||||
aliases[alias] = path.resolve(__dirname, aliasPath);
|
||||
return aliases;
|
||||
}, {});
|
||||
|
||||
const postcssPlugins = [
|
||||
require("postcss-import")({
|
||||
resolve(id, base, options) {
|
||||
// Since WebStorm doesn't resolve imports from node_modules without a tilde (~) prefix,
|
||||
// strip the tilde here to get the best of both worlds (webstorm support + postcss-import support)
|
||||
if (id[0] === "~") id = id.slice(1);
|
||||
// Call the original resolver after stripping the tilde
|
||||
return require("postcss-import/lib/resolve-id")(id, base, options);
|
||||
},
|
||||
}),
|
||||
require("postcss-nesting")(),
|
||||
require("tailwindcss")(),
|
||||
];
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
postcssPlugins.push(require("postcss-preset-env")(), require("cssnano")());
|
||||
}
|
||||
|
||||
let config = {
|
||||
entry: "./src/main.ts",
|
||||
output: {
|
||||
filename: "[name].[fullhash].js",
|
||||
path: targetDir,
|
||||
publicPath: "/",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// Vue / Babel / Typescript
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: "vue-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
whitespace: 'preserve', // not the default despite the docs saying so
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: babelOpts,
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: babelOpts,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
},
|
||||
|
||||
// Stylesheets
|
||||
{
|
||||
test: /\.p?css$/,
|
||||
use: [
|
||||
"vue-style-loader",
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
// ident: "postcss",
|
||||
postcssOptions: {
|
||||
plugins: postcssPlugins,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Images/files
|
||||
{
|
||||
test: /\.(png|jpg)$/i,
|
||||
use: {
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: "[name]-[hash].[ext]",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// HTML
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
esModule: false,
|
||||
...(process.env.NODE_ENV === "production" && {
|
||||
minimize: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "src/index.html",
|
||||
files: {
|
||||
css: ["./src/style/initial.pcss"],
|
||||
js: ["./src/main.ts"],
|
||||
},
|
||||
}),
|
||||
new webpack.EnvironmentPlugin(["API_URL"]),
|
||||
new webpack.DefinePlugin({
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: false,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".vue"],
|
||||
alias: pathAliases,
|
||||
roots: [path.resolve(__dirname, "src")],
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
config = merge(config, {
|
||||
mode: "production",
|
||||
devtool: "source-map",
|
||||
});
|
||||
} else {
|
||||
config = merge(config, {
|
||||
mode: "development",
|
||||
devtool: "eval",
|
||||
devServer: {
|
||||
allowedHosts: "all",
|
||||
historyApiFallback: true,
|
||||
port: 3002,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = config;
|
|
@ -12,6 +12,11 @@ server {
|
|||
# This is the address of the internal docker compose DNS server.
|
||||
resolver 127.0.0.11;
|
||||
proxy_pass $dashboard_upstream$uri$is_args$args;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
location /api {
|
||||
|
|
10387
package-lock.json
generated
10387
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -27,5 +27,8 @@
|
|||
"shared",
|
||||
"backend",
|
||||
"dashboard"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue