3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

dashboard: use webpack for builds; use tailwindcss instead of bulma; all sorts of tweaks

This commit is contained in:
Dragory 2019-10-10 21:58:00 +03:00
parent 028786d348
commit 577500af92
42 changed files with 4813 additions and 3174 deletions

View file

@ -1,8 +0,0 @@
{
"plugins": [
["transform-runtime", {
"regenerator": true
}],
"transform-object-rest-spread"
]
}

File diff suppressed because it is too large Load diff

View file

@ -4,33 +4,45 @@
"description": "",
"private": true,
"scripts": {
"build": "rimraf dist && parcel build src/index.html --no-source-maps --out-dir dist",
"build-with-report": "cross-env GENERATE_BUNDLE_SIZE_REPORT=1 npm run build",
"build-debug": "rimraf dist && cross-env NODE_ENV=development parcel build src/index.html --no-minify --out-dir dist",
"watch": "parcel src/index.html"
"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"
},
"devDependencies": {
"@vue/component-compiler-utils": "^3.0.0",
"babel-core": "^6.26.3",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"@babel/core": "^7.6.3",
"@babel/preset-env": "^7.6.3",
"@babel/preset-typescript": "^7.6.0",
"babel-loader": "^8.0.6",
"cross-env": "^5.2.0",
"parcel-bundler": "^1.12.3",
"parcel-plugin-bundle-visualiser": "git://github.com/Dragory/parcel-plugin-bundle-visualiser.git#explicit-env-var",
"sass": "^1.21.0",
"vue-template-compiler": "^2.6.10"
"css-loader": "^3.2.0",
"cssnano": "^4.1.10",
"dotenv-webpack": "^1.7.0",
"file-loader": "^4.2.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^4.0.0-beta.8",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-nesting": "^7.0.1",
"postcss-preset-env": "^6.7.0",
"source-map-loader": "^0.2.4",
"tailwindcss": "^1.1.2",
"ts-loader": "^6.2.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.41.0",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"buefy": "^0.7.10",
"bulma": "^0.7.5",
"bulmaswatch": "^0.7.2",
"highlight.js": "^9.15.10",
"js-cookie": "^2.2.0",
"js-yaml": "^3.13.1",
"marked": "^0.7.0",
"modern-css-reset": "^1.0.4",
"vue": "^2.6.10",
"vue-highlightjs": "git://github.com/Dragory/vue-highlightjs.git#pass-hljs-instance",
"vue-hot-reload-api": "^2.3.3",
"vue-material-design-icons": "^4.1.0",
"vue-router": "^3.0.6",
"vue2-ace-editor": "0.0.14",
"vuex": "^3.1.1"

View file

@ -11,7 +11,7 @@ const isAuthenticated = async () => {
export const authGuard: NavigationGuard = async (to, from, next) => {
if (await isAuthenticated()) return next();
next("/");
window.location.href = `${process.env.API_URL}/auth/login`;
};
export const loginCallbackGuard: NavigationGuard = async (to, from, next) => {
@ -19,12 +19,7 @@ export const loginCallbackGuard: NavigationGuard = async (to, from, next) => {
await RootStore.dispatch("auth/setApiKey", to.query.apiKey);
next("/dashboard");
} else {
next({
path: "/",
query: {
error: "noaccess",
},
});
window.location.href = `/?error=noAccess`;
}
};

View file

@ -1,6 +1,3 @@
<template>
<router-view></router-view>
</template>
<script>
</script>

View file

@ -0,0 +1,119 @@
<template>
<div class="expandable mb-4 bg-gray-800 border border-gray-600 rounded overflow-hidden" ref="root" v-bind:class="{'shadow-xl': isOpen}">
<div role="button" class="title p-2" v-on:click="toggle">
<chevron-down class="icon" v-bind:class="{'icon-open': isOpen}" />
<span class="title-text"><slot name="title"></slot></span>
</div>
<div class="content border-t border-gray-700" ref="content">
<div class="p-4 pb-0">
<slot name="content"></slot>
</div>
</div>
</div>
</template>
<style scoped>
:root {
--animation-time: 400ms;
--target-height: auto;
}
.expandable {
transition: box-shadow var(--animation-time);
}
.title {
&:hover {
& .title-text {
@apply underline;
}
}
}
.icon {
transition: transform var(--animation-time);
transform-origin: 50% 60%;
}
.icon-open {
transform: rotate(179deg);
}
.content {
overflow: hidden;
display: none;
}
@keyframes open {
0% { height: 0; }
100% { height: var(--target-height); }
}
@keyframes close {
100% { height: 0; }
0% { height: var(--target-height); }
}
.opening {
animation: open var(--animation-time) ease-in-out;
}
.closing {
animation: close var(--animation-time) ease-in-out;
}
</style>
<script type="ts">
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue';
const ANIMATION_TIME = 400;
export default {
components: { ChevronDown },
mounted() {
this.$refs.root.style.setProperty('--animation-time', `${ANIMATION_TIME}ms`);
},
data() {
return {
isOpen: false,
animating: false,
};
},
methods: {
toggle() {
if (this.isOpen) this.close();
else this.open();
},
open() {
if (this.animating) return;
this.animating = true;
this.isOpen = true;
this.$refs.content.style.display = 'block';
const targetHeight = this.$refs.content.clientHeight;
this.$refs.content.style.setProperty('--target-height', `${targetHeight}px`);
this.$refs.content.classList.add('opening');
setTimeout(() => {
this.$refs.content.classList.remove('opening');
this.animating = false;
}, ANIMATION_TIME);
},
close() {
if (this.animating) return;
this.animating = true;
this.isOpen = false;
const targetHeight = this.$refs.content.clientHeight;
this.$refs.content.style.setProperty('--target-height', `${targetHeight}px`);
this.$refs.content.classList.add('closing');
setTimeout(() => {
this.$refs.content.classList.remove('closing');
this.$refs.content.style.display = 'none';
this.animating = false;
}, ANIMATION_TIME);
},
},
};
</script>

View file

@ -1,35 +0,0 @@
<template>
<div class="splash">
<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">
<router-link class="btn" to="/login">Dashboard</router-link>
<router-link class="btn" to="/docs">Documentation</router-link>
</div>
<div class="error" v-if="error">
<strong>Error</strong>
<div v-if="error === 'noaccess'">No access</div>
</div>
</div>
</div>
</div>
</template>
<script>
import "../style/splash.scss";
export default {
computed: {
error() {
return this.$route.query.error;
},
},
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<li class="py-2 px-4 hover:text-gray-200" :class="{'pb-1 border-b border-gray-400 text-gray-200': active, 'text-gray-500': !active}">
<slot></slot>
</li>
</template>
<script lang="ts">
export default {
props: ["active"],
};
</script>

View file

@ -0,0 +1,5 @@
<template>
<ul class="list-none flex border-b border-gray-600 mb-4">
<slot></slot>
</ul>
</template>

View file

@ -20,6 +20,15 @@
</div>
</template>
<style scoped>
.ace_editor {
box-shadow: 0 2px 16px -4px #0000009e;
border-radius: 8px;
border: 1px solid #181818;
margin: 16px 0;
}
</style>
<script>
import {mapState} from "vuex";
import {ApiError} from "../../api";

View file

@ -45,7 +45,7 @@
</style>
<script>
import "../../style/dashboard.scss";
// import "../../style/dashboard.scss";
export default {
methods: {

View file

@ -1,83 +1,83 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Argument Types</h1>
<p class="mb-1">
<h1>Argument Types</h1>
<p>
This page details the different argument types available for commands.
</p>
<h2 id="string" class="z-title is-2 mt-2 mb-1">string</h2>
<p class="mb-1">
<h2 id="string">string</h2>
<p>
Any text
</p>
<h2 id="number" class="z-title is-2 mt-2 mb-1">number</h2>
<p class="mb-1">
<h2 id="number">number</h2>
<p>
Any number
</p>
<h2 id="user" class="z-title is-2 mt-2 mb-1">user</h2>
<p class="mb-1">
<h2 id="user">user</h2>
<p>
Anything that uniquely identifies a user. This includes:
</p>
<ul class="z-list z-ul mb-1">
<ul>
<li>User ID <code>108552944961454080</code></li>
<li>User mention <code>@Dark#1010</code></li>
<li>Loose user mention <code>Dark#1010</code></li>
</ul>
<h2 id="userId" class="z-title is-2 mt-2 mb-1">userId</h2>
<p class="mb-1">
<h2 id="userId">userId</h2>
<p>
A valid user ID, e.g. <code>108552944961454080</code>
</p>
<h2 id="channel" class="z-title is-2 mt-2 mb-1">channel</h2>
<p class="mb-1">
<h2 id="channel">channel</h2>
<p>
Anything that uniquely identifies a channel. This includes:
</p>
<ul class="z-list z-ul mb-1">
<ul>
<li>Channel ID <code>473087035574321152</code></li>
<li>Channel mention <code>#my-channel</code></li>
</ul>
<h2 id="channelId" class="z-title is-2 mt-2 mb-1">channelId</h2>
<p class="mb-1">
<h2 id="channelId">channelId</h2>
<p>
A valid channel ID, e.g. <code>473087035574321152</code>
</p>
<h2 id="role" class="z-title is-2 mt-2 mb-1">role</h2>
<p class="mb-1">
<h2 id="role">role</h2>
<p>
Anything that uniquely identifies a role. This includes:
</p>
<ul class="z-list z-ul mb-1">
<li>Role ID <code>473085927053590538</code></li>
<ul>
<li>Role ID <code class="inline-code">473085927053590538</code></li>
<li>Role mention <code>@MyRole</code></li>
</ul>
<h2 id="member" class="z-title is-2 mt-2 mb-1">member</h2>
<p class="mb-1">
<h2 id="member">member</h2>
<p>
Anything that uniquely identifies a member currently on the server. This includes:
</p>
<ul class="z-list z-ul mb-1">
<ul>
<li>User ID <code>108552944961454080</code></li>
<li>User Mention <code>@Dark#1010</code></li>
<li>Loose user mention <code>Dark#1010</code></li>
</ul>
<h2 id="resolvedMember" class="z-title is-2 mt-2 mb-1">resolvedMember</h2>
<p class="mb-1">
<h2 id="resolvedMember">resolvedMember</h2>
<p>
See <code>member</code> above
</p>
<h2 id="delay" class="z-title is-2 mt-2 mb-1">delay</h2>
<p class="mb-1">
<h2 id="delay">delay</h2>
<p>
A delay is used to specify an amount of time. It uses simple letters to specify time durations.<br>
For example, <code>2d15h27m3s</code> would be 2 days, 15 hours, 27 minutes and 3 seconds.
</p>
<p class="mb-1">
<p>
Note that the delay should always be written as 1 word, without spaces!
</p>
<b-collapse :open="false" class="card mb-1">
<div :open="false" class="card mb-1"> <!-- b-collapse -->
<div slot="trigger" slot-scope="props" class="card-header" role="button">
<p class="card-header-title">Additional Information</p>
<a class="card-header-icon">
@ -103,7 +103,7 @@
</ul>
</div>
</div>
</b-collapse>
</div>
</div>
</template>

View file

@ -1,20 +1,7 @@
<template>
<pre class="codeblock" v-highlightjs><code :class="lang" v-trim-code="trim"><slot></slot></code></pre>
<pre class="codeblock" v-highlightjs><code :class="lang" v-trim-indents="trim"><slot></slot></code></pre>
</template>
<style scoped>
.codeblock {
border-radius: 3px;
padding: 16px;
max-width: 970px; /* FIXME: temp fix for overflowing code blocks, look into properly later */
}
.hljs {
background: transparent;
padding: 0;
}
</style>
<script>
export default {
props: ["lang", "trim"],

View file

@ -1,15 +1,17 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Configuration format</h1>
<p class="mb-1">
<h1>Configuration format</h1>
<p>
This is the basic format of the bot configuration for a guild. The basic breakdown is:
</p>
<ol class="z-list mb-1">
<ul>
<li>Prefix (i.e. what character is preceding each command)</li>
<li>Permission levels (see <router-link to="/docs/permissions">Permissions</router-link> for more info)</li>
<li>Plugin-specific configuration (see <router-link to="/docs/plugin-configuration">Plugin configuration</router-link> for more info)</li>
</ol>
<CodeBlock lang="yaml" trim="4">
</ul>
<CodeBlock lang="yaml" trim="start">
prefix: "!"
# role id: level
@ -33,8 +35,8 @@
</div>
</template>
<script>
import CodeBlock from "./CodeBlock";
<script lang="ts">
import CodeBlock from "./CodeBlock.vue";
export default {
components: { CodeBlock },

View file

@ -1,168 +1,134 @@
<template>
<div class="docs docs-cloak">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<div class="navbar-item">
<img class="docs-logo" src="../../img/logo.png" alt="" aria-hidden="true">
<h1 class="docs-title">Zeppelin Documentation</h1>
</div>
<div class="docs">
<div class="container mx-auto px-4 py-2">
<!-- 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="../../img/logo.png" alt="" aria-hidden="true">
<h1 class="flex-auto">Zeppelin Documentation</h1>
</div>
<div class="flex-1 flex items-center justify-end">
<router-link
to="/dashboard"
role="menuitem"
class="py-1 px-2 rounded hover:bg-gray-700">
Go to dashboard
</router-link>
</div>
</nav>
<div class="navbar-menu is-active">
<div class="navbar-end">
<router-link to="/dashboard" class="navbar-item">Go to dashboard</router-link>
</div>
</div>
<!-- WIP bar -->
<div class="mt-6 px-3 py-2 rounded bg-gray-800 shadow-md">
<i class="mdi mdi-alert mr-1" title="Note"></i>
This documentation is a work in progress.
</div>
</nav>
<div class="wip-bar">
<i class="mdi mdi-alert"></i>
<strong>Note!</strong> This documentation is a work in progress.
</div>
<div class="wrapper">
<div class="docs-sidebar">
<div class="docs-sidebar-content">
<aside class="menu">
<p class="menu-label">General</p>
<ul class="menu-list">
<li><router-link to="/docs/introduction">Introduction</router-link></li>
<li><router-link to="/docs/configuration-format">Configuration format</router-link></li>
<li><router-link to="/docs/plugin-configuration">Plugin configuration</router-link></li>
<li><router-link to="/docs/permissions">Permissions</router-link></li>
</ul>
<p class="menu-label">Reference</p>
<ul class="menu-list">
<li><router-link to="/docs/reference/argument-types">Argument types</router-link></li>
</ul>
<p class="menu-label">Setup guides</p>
<ul class="menu-list">
<li><router-link to="/docs/setup-guides/logs">Logs</router-link></li>
<li><router-link to="/docs/setup-guides/moderation">Moderation</router-link></li>
</ul>
<p class="menu-label">Plugins</p>
<ul class="menu-list">
<li v-for="plugin in plugins">
<router-link :to="'/docs/plugins/' + plugin.name">{{ plugin.info.prettyName || plugin.name }}</router-link>
<!-- Content wrapper -->
<div class="flex items-start mt-8">
<!-- Sidebar -->
<nav class="docs-sidebar flex-none px-4 pt-2 pb-3 mr-8 border border-gray-700 rounded bg-gray-800 shadow-md">
<div role="none" v-for="(group, index) in menu">
<h1 class="font-bold" :aria-owns="'menu-group-' + index" :class="{'mt-4': index !== 0}">{{ group.label }}</h1>
<ul v-bind:id="'menu-group-' + index" role="group" class="list-none pl-2">
<li role="none" v-for="item in group.items">
<router-link role="menuitem" :to="item.to" class="text-gray-300 hover:text-gray-500">{{ item.label }}</router-link>
</li>
</ul>
</aside>
</div>
</nav>
<!-- Content -->
<div class="docs-content flex-auto overflow-x-hidden">
<router-view :key="$route.fullPath"></router-view>
</div>
</div>
<div class="docs-main">
<router-view :key="$route.fullPath"></router-view>
</div>
</div>
</div>
</template>
<style scoped>
.docs-cloak {
/* Replaced by "visible" in docs.scss */
visibility: hidden;
}
.docs {
width: 100%;
max-width: 1280px;
margin: 20px auto;
}
.navbar {
border: 1px solid #4e5d6c;
border-radius: 3px;
margin-bottom: 24px;
padding: 0 16px;
}
.docs-logo {
margin-right: 12px;
}
.docs-title {
font-weight: 600;
}
.wip-bar {
padding: 4px 10px;
margin-bottom: 24px;
background-color: #2B3E50;
border-radius: 4px;
}
.wip-bar i {
color: #fdd7a5;
font-size: 24px;
vertical-align: -3px;
margin-right: 6px;
}
.wrapper {
display: flex;
}
.docs-sidebar {
flex: 0 0 280px;
}
.docs-sidebar-content {
/* can't scroll with a long list before reaching the end of the page, figure out */
/*position: sticky;*/
/*top: 20px;*/
}
.docs-sidebar .menu {
padding: 12px 16px;
border: 1px solid #4e5d6c;
background-color: #2b3e50;
border-radius: 3px;
}
.docs-sidebar .menu-label {
font-weight: 600;
}
.docs-main {
flex: 1 1 100%;
padding: 0 24px 24px;
}
.docs-main >>> h4 {
margin-top: 1.25em; /* ? */
}
</style>
<script>
<script lang="ts">
import Vue from "vue";
import hljs from "highlight.js/lib/highlight.js";
import hljsYaml from "highlight.js/lib/languages/yaml.js";
import VueHighlightJS from "vue-highlightjs";
import Buefy from "buefy";
import {mapState} from "vuex";
import "../../style/icons.scss";
import "buefy/dist/buefy.css";
import "highlight.js/styles/ocean.css";
import "../../directives/trim-code";
import "../../style/docs.scss";
type TMenuItem = {
to: string;
label: string;
};
type TMenuGroup = {
label: string;
items: TMenuItem[];
};
type TMenu = TMenuGroup[];
hljs.registerLanguage("yaml", hljsYaml);
Vue.use(VueHighlightJS, { hljs });
Vue.use(Buefy);
const menu: TMenu = [
{
label: 'General',
items: [
{
to: '/docs/introduction',
label: 'Introduction',
},
{
to: '/docs/configuration-format',
label: 'Configuration format',
},
{
to: '/docs/plugin-configuration',
label: 'Plugin configuration',
},
{
to: '/docs/permissions',
label: 'Permissions',
},
],
},
{
label: 'Reference',
items: [
{
to: '/docs/reference/argument-types',
label: 'Argument types',
},
],
},
{
label: 'Setup guides',
items: [
{
to: '/docs/setup-guides/logs',
label: 'Logs',
},
{
to: '/docs/setup-guides/moderation',
label: 'Moderation',
},
],
},
];
export default {
async mounted() {
await this.$store.dispatch("docs/loadAllPlugins");
},
computed: {
...mapState('docs', {
plugins: 'allPlugins',
}),
menu() {
return [
...menu,
{
label: 'Plugins',
items: this.plugins.map(plugin => ({
label: plugin.info.prettyName || plugin.name,
to: `/docs/plugins/${plugin.name}`,
})),
}
];
},
},
};
</script>

View file

@ -1,29 +1,29 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Introduction</h1>
<p class="mb-1">
<h1>Introduction</h1>
<p>
Zeppelin is a private moderation bot for Discord, designed with large servers and reliability in mind.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Getting the bot</h2>
<p class="mb-1">
<h2>Getting the bot</h2>
<p>
Since the bot is currently private, access to the bot is granted on a case by case basis.<br>
There are plans to streamline this process in the future.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Configuration</h2>
<p class="mb-1">
<h2>Configuration</h2>
<p>
All Zeppelin configuration is done through the dashboard by editing a YAML config file. By default, only the server
owner has access to this, but they can give other users access as they see fit. See <router-link to="/docs/configuration-format">Configuration format</router-link> for more details.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Plugins</h2>
<p class="mb-1">
<h2>Plugins</h2>
<p>
Zeppelin is divided into plugins: grouped functionality that can be enabled/disabled as needed, and that have their own configurations.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Commands</h2>
<p class="mb-1">
<h2>Commands</h2>
<p>
The commands for each plugin are listed on the plugin's page (see "Plugins" on the menu). On these pages, the command prefix is assumed to be <code>!</code> but this can be changed on a per-server basis.
</p>
</div>

View file

@ -1,23 +1,23 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Permissions</h1>
<p class="mb-1">
<h1>Permissions</h1>
<p>
Permissions in Zeppelin are simply values in plugin configuration that are checked when the command is used.
These values can be changed with overrides (see <router-link to="/docs/plugin-configuration">Plugin configuration</router-link> for more info)
and can depend on e.g. user id, role id, channel id, category id, or <strong>permission level</strong>.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Permission levels</h2>
<p class="mb-1">
<h2>Permission levels</h2>
<p>
The simplest way to control access to bot commands and features is via permission levels.
These levels are simply a number (usually between 0 and 100), based on the user's roles or user id, that can then
be used in permission overrides. By default, several commands are "moderator only" (level 50 and up) or "admin only" (level 100 and up).
</p>
<p class="mb-1">
<p>
Additionally, having a higher permission level means that certain commands (such as !ban) can't be used against
you by users with a lower or equal permission level (so e.g. moderators can't ban each other or admins above them).
</p>
<p class="mb-1">
<p>
Permission levels are defined in the config in the <strong>levels</strong> section. For example:
</p>
@ -28,14 +28,14 @@
"172950000412655616": 50 # Example mod
</CodeBlock>
<h2 class="z-title is-2 mt-2 mb-1">Examples</h2>
<h2>Examples</h2>
<h3 class="z-title is-3 mb-1">Basic overrides</h3>
<p class="mb-1">
<h3>Basic overrides</h3>
<p>
For this example, let's assume we have a plugin called <code>cats</code> which has a command <code>!cat</code> locked behind the permission <code>can_cat</code>.
Let's say that by default, the plugin allows anyone to use <code>!cat</code>, but we want to restrict it to moderators only.
</p>
<p class="mb-1">
<p>
Here's what the configuration for this would look like:
</p>
@ -51,7 +51,7 @@
can_cat: true
</CodeBlock>
<h3 class="z-title is-3 mt-2 mb-1">Replacing defaults</h3>
<h3>Replacing defaults</h3>
<p class="mb-1">
In this example, let's assume you don't want to use the default permission levels of 50 and 100 for mods and admins respectively.
Let's say you're using various incremental levels instead: 10, 20, 30, 40, 50...<br>

View file

@ -3,21 +3,19 @@
Loading...
</div>
<div v-else>
<h1 class="z-title is-1 mb-1">{{ data.info.prettyName || data.name }}</h1>
<h1>{{ data.info.prettyName || data.name }}</h1>
<!-- Description -->
<MarkdownBlock :content="data.info.description" class="content"></MarkdownBlock>
<div class="tabs">
<ul>
<li v-bind:class="{'is-active': tab === 'usage'}">
<router-link v-bind:to="'/docs/plugins/' + pluginName + '/usage'">Usage</router-link>
</li>
<li v-bind:class="{'is-active': tab === 'configuration'}">
<router-link v-bind:to="'/docs/plugins/' + pluginName + '/configuration'">Configuration</router-link>
</li>
</ul>
</div>
<Tabs>
<Tab :active="tab === 'usage'">
<router-link v-bind:to="'/docs/plugins/' + pluginName + '/usage'">Usage</router-link>
</Tab>
<Tab :active="tab === 'configuration'">
<router-link v-bind:to="'/docs/plugins/' + pluginName + '/configuration'">Configuration</router-link>
</Tab>
</Tabs>
<!-- Usage tab -->
<div class="usage" v-if="tab === 'usage'">
@ -28,38 +26,33 @@
<!-- Usage guide -->
<div v-if="data.info.usageGuide">
<h2 id="usage-guide" class="z-title is-2 mt-2 mb-1">Usage guide</h2>
<h2 id="usage-guide">Usage guide</h2>
<MarkdownBlock :content="data.info.usageGuide" class="content"></MarkdownBlock>
</div>
<!-- Command list -->
<div v-if="data.commands.length">
<h2 id="commands" class="z-title is-2 mt-2 mb-1">Commands</h2>
<div v-for="command in data.commands">
<h3 class="z-title is-3 mt-2 mb-1">!{{ command.trigger }}</h3>
<h2 id="commands">Commands</h2>
<div v-for="command in data.commands" class="mb-4">
<h3 class="text-xl">!{{ command.trigger }}</h3>
<div v-if="command.config.extra.requiredPermission">
Permission: <code>{{ command.config.extra.requiredPermission }}</code>
Permission: <code class="inline-code">{{ command.config.extra.requiredPermission }}</code>
</div>
<div v-if="command.config.extra.info && command.config.extra.info.basicUsage">
Basic usage: <code>{{ command.config.extra.info.basicUsage }}</code>
Basic usage: <code class="inline-code">{{ command.config.extra.info.basicUsage }}</code>
</div>
<div v-if="command.config.aliases && command.config.aliases.length">
Shortcut:
<code style="margin-right: 4px" v-for="alias in command.config.aliases">!{{ alias }}</code>
<code class="inline-code" style="margin-right: 4px" v-for="alias in command.config.aliases">!{{ alias }}</code>
</div>
<MarkdownBlock v-if="command.config.info && command.config.info.description" :content="command.config.info.description" class="content mt-1 mb-1"></MarkdownBlock>
<MarkdownBlock v-if="command.config.info && command.config.info.description" :content="command.config.info.description" class="content mb-4"></MarkdownBlock>
<b-collapse :open="false" class="card mt-1 mb-1">
<div slot="trigger" slot-scope="props" class="card-header" role="button">
<p class="card-header-title">Additional information</p>
<a class="card-header-icon">
<b-icon :icon="props.open ? 'menu-down' : 'menu-up'"></b-icon>
</a>
</div>
<div class="card-content">
<Expandable class="mt-4">
<template v-slot:title>Additional information</template>
<template v-slot:content>
Signatures:
<ul class="z-list z-ul">
<ul>
<li>
<code>
!{{ command.trigger }}
@ -70,7 +63,7 @@
<div class="mt-2" v-if="command.parameters.length">
Command arguments:
<ul class="z-list z-ul">
<ul>
<li v-for="param in command.parameters">
<code>{{ renderParameter(param) }}</code>
<router-link :to="'/docs/reference/argument-types#' + (param.type || 'string')">{{ param.type || 'string' }}</router-link>
@ -95,8 +88,8 @@
</li>
</ul>
</div>
</div>
</b-collapse>
</template>
</Expandable>
</div>
</div>
</div>
@ -104,26 +97,26 @@
<!-- Configuration tab -->
<div class="configuration" v-if="tab === 'configuration'">
<!-- Basic config info -->
<p class="mb-1">
<p>
Name in config: <code>{{ data.name }}</code>
</p>
<p class="mt-1 mb-1">
<p>
To enable this plugin with default configuration, add <code>{{ data.name }}: {}</code> to the <code>plugins</code> list in config
</p>
<!-- Configuration guide -->
<div v-if="data.info.configurationGuide">
<h2 id="configuration-guide" class="z-title is-2 mt-2 mb-1">Configuration guide</h2>
<h2 id="configuration-guide">Configuration guide</h2>
<MarkdownBlock :content="data.info.configurationGuide" class="content"></MarkdownBlock>
</div>
<!-- Default configuration -->
<h2 id="default-configuration" class="z-title is-2 mt-2 mb-1">Default configuration</h2>
<h2 id="default-configuration">Default configuration</h2>
<CodeBlock lang="yaml">{{ renderConfiguration(data.defaultOptions) }}</CodeBlock>
<!-- Config schema -->
<h2 id="config-schema" class="z-title is-2 mt-2 mb-1">Config schema</h2>
<b-collapse :open="false" class="card mt-1 mb-1">
<h2 id="config-schema">Config schema</h2>
<div :open="false" class="card mt-1 mb-1"> <!-- b-collapse -->
<div slot="trigger" slot-scope="props" class="card-header" role="button">
<p class="card-header-title">Click to expand</p>
<a class="card-header-icon">
@ -133,23 +126,27 @@
<div class="card-content">
<CodeBlock lang="plain">{{ data.configSchema }}</CodeBlock>
</div>
</b-collapse>
</div>
</div>
</div>
</template>
<script>
<script lang="ts">
import Vue from "vue";
import {mapState} from "vuex";
import yaml from "js-yaml";
import CodeBlock from "./CodeBlock";
import MarkdownBlock from "./MarkdownBlock";
import CodeBlock from "./CodeBlock.vue";
import MarkdownBlock from "./MarkdownBlock.vue";
import Tabs from "../Tabs.vue";
import Tab from "../Tab.vue";
import Expandable from "../Expandable.vue";
import { DocsState } from "../../store/types";
const validTabs = ['usage', 'configuration'];
const defaultTab = 'usage';
export default {
components: { CodeBlock, MarkdownBlock },
components: { CodeBlock, MarkdownBlock, Tabs, Tab, Expandable },
async mounted() {
this.loading = true;
@ -201,7 +198,7 @@
},
computed: {
...mapState("docs", {
data(state) {
data(state: DocsState) {
return state.plugins[this.pluginName];
},
hasUsageInfo() {

View file

@ -1,20 +1,20 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Plugin configuration</h1>
<p class="mb-1">
<h1>Plugin configuration</h1>
<p>
Each plugin in Zeppelin has its own configuration options. In the config editor, you can both set the default config
and overrides based on specific conditions. Permissions are also just values in the plugin's config, and thus follow
the same rules with overrides etc. as other options (see <router-link to="/docs/permissions">Permissions</router-link> for more info).
</p>
<p class="mb-1">
<p>
Information about each plugin's options can be found on the plugin's page on the sidebar. See <router-link to="/docs/configuration-format">Configuration format</router-link> for an example of a full config.
</p>
<h2 class="z-title is-2 mt-2 mb-1">Overrides</h2>
<p class="mb-1">
<h2>Overrides</h2>
<p>
Overrides are the primary mechanism of changing options and permissions based on permission levels, roles, channels, user ids, etc.
</p>
<p class="mb-1">
<p>
Here's an example demonstrating different types of overrides:
</p>
@ -74,8 +74,8 @@
</div>
</template>
<script>
import CodeBlock from "./CodeBlock";
<script lang="ts">
import CodeBlock from "./CodeBlock.vue";
export default {
components: { CodeBlock },

View file

@ -1,7 +1,7 @@
<template>
<div>
<h1 class="z-title is-1 mb-1">Work in progress</h1>
<p class="mb-1">
<h1>Work in progress</h1>
<p>
This page is a work in progress.
</p>
</div>

View file

@ -1,11 +0,0 @@
import Vue from "vue";
Vue.directive("trim-code", {
bind(el, binding) {
el.innerHTML = el.innerHTML
.replace(/(^\n+|\n+$)/g, "")
.split("\n")
.map(line => line.slice(binding.value))
.join("\n");
},
});

View file

@ -0,0 +1,25 @@
import Vue from "vue";
Vue.directive("trim-indents", {
bind(el, binding) {
const withoutStartEndWhitespace = el.innerHTML.replace(/(^\n+|\n+$)/g, "");
const mode = binding.value != null ? binding.value : "start";
let spacesToTrim;
if (mode === "start") {
const match = withoutStartEndWhitespace.match(/^\s+/);
spacesToTrim = match ? match[0].length : 0;
} else if (mode === "end") {
const match = withoutStartEndWhitespace.match(/\s+$/);
spacesToTrim = match ? match[0].length : 0;
} else {
spacesToTrim = parseInt(mode, 10);
}
el.innerHTML = withoutStartEndWhitespace
.split("\n")
.map(line => line.slice(spacesToTrim))
.join("\n");
},
});

View file

@ -8,8 +8,11 @@
<title>Zeppelin Dashboard</title>
</head>
<body>
<div id="app"></div>
<noscript>
<h1>Zeppelin</h1>
The Zeppelin dashboard requires JavaScript to load.
</noscript>
<script src="./main.ts"></script>
<div id="app"></div>
</body>
</html>

40
dashboard/src/init-vue.ts Normal file
View file

@ -0,0 +1,40 @@
import "./style/app.pcss";
import Vue from "vue";
import hljs from "highlight.js/lib/highlight.js";
import hljsYaml from "highlight.js/lib/languages/yaml.js";
import VueHighlightJS from "vue-highlightjs";
import "highlight.js/styles/ocean.css";
import { RootStore } from "./store";
import { router } from "./routes";
import "./directives/trim-indents";
import App from "./components/App.vue";
// Set up a read-only global variable to access specific env vars
Vue.mixin({
data() {
return {
get env() {
return Object.freeze({
API_URL: process.env.API_URL,
});
},
};
},
});
hljs.registerLanguage("yaml", hljsYaml);
Vue.use(VueHighlightJS, { hljs });
const app = new Vue({
router,
store: RootStore,
el: "#app",
render(h) {
return h(App);
},
});

View file

@ -1,29 +1,32 @@
import "./style/base.scss";
import "./style/initial.pcss";
const splashHtml = require("./splash.html");
import Vue from "vue";
import { RootStore } from "./store";
import { router } from "./routes";
if (window.location.pathname !== "/") {
import("./init-vue");
} else {
// @ts-ignore
document.querySelector("#app").innerHTML = splashHtml;
// Set up a read-only global variable to access specific env vars
Vue.mixin({
data() {
return {
get env() {
return Object.freeze({
API_URL: process.env.API_URL,
});
},
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.",
};
},
});
import App from "./components/App.vue";
const app = new Vue({
router,
store: RootStore,
el: "#app",
render(h) {
return h(App);
},
});
const errorMessageElem = document.createElement("div");
errorMessageElem.classList.add("message");
errorMessageElem.innerText = errorMessages[queryParams.error] || "Unexpected error";
errorElement.appendChild(errorMessageElem);
}
}

View file

@ -1,6 +1,5 @@
import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import Splash from "./components/Splash.vue";
import { authGuard, authRedirectGuard, loginCallbackGuard } from "./auth";
Vue.use(VueRouter);
@ -8,7 +7,6 @@ Vue.use(VueRouter);
export const router = new VueRouter({
mode: "history",
routes: [
{ path: "/", component: Splash },
{ path: "/login", beforeEnter: authRedirectGuard },
{ path: "/login-callback", beforeEnter: loginCallbackGuard },

18
dashboard/src/splash.html Normal file
View file

@ -0,0 +1,18 @@
<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="/login">Dashboard</a>
<a class="btn" href="/docs">Documentation</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,18 @@
@import "~tailwindcss/base.css";
@import "~tailwindcss/components.css";
@import "~tailwindcss/utilities.css";
@import "~vue-material-design-icons/styles.css";
@import "components.pcss";
@import "docs.pcss";
body {
overflow-y: scroll;
@apply bg-gray-900;
@apply text-gray-300;
@apply text-base;
@apply p-4;
}

View file

@ -0,0 +1,4 @@
body {
font: normal 18px/1.5 sans-serif;
font-family: system, -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
}

View file

@ -1,6 +0,0 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600&display=swap');
@import "~bulma/sass/base/minireset";
body {
font: normal 16px/1.4 'Open Sans', sans-serif;
}

View file

@ -0,0 +1,21 @@
.inline-code {
@apply inline-block;
@apply bg-gray-800;
@apply px-1;
@apply rounded;
@apply text-sm;
}
.codeblock {
@apply bg-gray-800;
@apply p-3;
@apply mb-4;
@apply rounded;
@apply text-sm;
@apply shadow-md;
& .hljs {
@apply bg-transparent;
@apply p-0;
}
}

View file

@ -1,9 +1,78 @@
@import "~buefy/dist/buefy.css";
@import "~highlight.js/styles/ocean.css";
$bulmaswatch-import-font: false;
$family-primary: 'Open Sans', sans-serif;
$list-background-color: transparent;
$size-1: 2.5rem;
$size-2: 2rem;
$size-3: 1.5rem;
$size-4: 1.25rem;
@import "~bulmaswatch/superhero/_variables";
$tabs-link-color: $grey-light;
$tabs-link-active-color: $grey-lighter;
$tabs-link-active-border-bottom-color: $grey-lighter;
@import "~bulma/bulma";
@import "~bulmaswatch/superhero/_overrides";
.dashboard-cloak {
.init-cloak {
visibility: visible !important;
}
.z-title {
line-height: 1.125;
&.is-1 { font-size: $size-1; }
&.is-2 { font-size: $size-2; }
&.is-3 { font-size: $size-3; }
&.is-4 { font-size: $size-4; }
&.is-5 { font-size: $size-5; }
&.is-6 { font-size: $size-6; }
}
.z-list {
margin-left: 1.5rem;
}
.z-ul {
list-style: disc;
}
.mt-1 { margin-top: 1rem; }
.mt-2 { margin-top: 1.5rem; }
.mt-3 { margin-top: 2rem; }
.mb-1 { margin-bottom: 1rem; }
.mb-2 { margin-bottom: 1.5rem; }
.mb-3 { margin-bottom: 2rem; }
.codeblock,
.content .codeblock {
border-radius: 3px;
padding: 16px;
max-width: 970px; /* FIXME: temp fix for overflowing code blocks, look into properly later */
}
.codeblock .hljs {
background: transparent;
padding: 0;
}
.menu-label {
&:not(:first-child) {
margin-top: 1.4em;
}
&:not(:last-child) {
margin-bottom: 0.4em;
}
}
.menu-list .router-link-active {
text-decoration: underline;
}

View file

@ -0,0 +1,60 @@
.docs-sidebar {
& .router-link-active {
@apply underline;
}
}
.docs-content {
& h1 {
@apply text-5xl;
@apply font-semibold;
@apply leading-none;
@apply pb-4;
}
& h2 {
@apply text-2xl;
@apply font-semibold;
@apply pt-2;
@apply pb-1;
}
& h3 {
@apply font-semibold;
@apply pb-1;
}
& p {
@apply pb-4;
& code {
@apply inline-code;
}
}
& a:not([class]) {
@apply text-blue-400;
@apply underline;
&:hover {
@apply text-blue-200;
}
}
& ul:not([class]) {
@apply list-disc;
@apply mb-4;
& li {
@apply ml-6;
}
& code {
@apply inline-code;
}
}
& .expandable {
max-width: 600px;
}
}

View file

@ -1,77 +0,0 @@
@import url('https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css');
$bulmaswatch-import-font: false;
$family-primary: 'Open Sans', sans-serif;
$list-background-color: transparent;
$size-1: 2.5rem;
$size-2: 2rem;
$size-3: 1.5rem;
$size-4: 1.25rem;
@import "~bulmaswatch/superhero/_variables";
$tabs-link-color: $grey-light;
$tabs-link-active-color: $grey-lighter;
$tabs-link-active-border-bottom-color: $grey-lighter;
@import "~bulma/bulma";
@import "~bulmaswatch/superhero/_overrides";
.docs-cloak {
visibility: visible !important;
}
.z-title {
line-height: 1.125;
&.is-1 { font-size: $size-1; }
&.is-2 { font-size: $size-2; }
&.is-3 { font-size: $size-3; }
&.is-4 { font-size: $size-4; }
&.is-5 { font-size: $size-5; }
&.is-6 { font-size: $size-6; }
}
.z-list {
margin-left: 1.5rem;
}
.z-ul {
list-style: disc;
}
.mt-1 { margin-top: 1rem; }
.mt-2 { margin-top: 1.5rem; }
.mt-3 { margin-top: 2rem; }
.mb-1 { margin-bottom: 1rem; }
.mb-2 { margin-bottom: 1.5rem; }
.mb-3 { margin-bottom: 2rem; }
.codeblock,
.content .codeblock {
border-radius: 3px;
padding: 16px;
max-width: 970px; /* FIXME: temp fix for overflowing code blocks, look into properly later */
}
.codeblock .hljs {
background: transparent;
padding: 0;
}
.menu-label {
&:not(:first-child) {
margin-top: 1.4em;
}
&:not(:last-child) {
margin-bottom: 0.4em;
}
}
.menu-list .router-link-active {
text-decoration: underline;
}

View file

@ -1 +0,0 @@
@import url("https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css");

View file

@ -0,0 +1,3 @@
@import "./reset.pcss";
@import "./base.pcss";
@import "./splash.pcss";

View file

@ -0,0 +1,48 @@
/* 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;
}
}

View file

@ -1,58 +1,82 @@
.splash {
width: 100vw;
height: 100vh;
padding: 16px;
background-color: #7289da;
background-image: linear-gradient(225deg, #7289da 0%, #5d70b4 100%);
color: #fff;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
flex-direction: column;
justify-content: flex-start;
align-items: center;
a {
& a {
color: #fff;
}
.wrapper {
flex: 0 1 750px;
& > #error {
width: 100%;
max-width: 750px;
flex-direction: row;
justify-content: center;
&.has-error {
display: flex;
}
& .message {
flex: 0 1 auto;
text-align: left;
padding: 8px 12px;
background-color: #404040;
border-radius: 4px;
box-shadow: 0 3px 12px -2px hsla(0, 0%, 0%, 0.2);
}
}
& .wrapper {
flex: 0 0 auto;
width: 750px;
max-width: 100%;
display: flex;
flex-direction: row;
align-items: start;
align-items: flex-start;
.logo-column {
& .logo-column {
flex: 0 0 auto;
}
.info-column {
& .info-column {
flex: 1 1 100%;
}
.logo {
& .logo {
width: 300px;
height: 300px;
margin-right: 64px;
margin-right: 48px;
}
h1 {
& h1 {
font-size: 80px;
font-weight: 300;
margin-top: 40px
}
.description {
& .description {
color: #f1f5ff;
}
.actions {
& .actions {
display: flex;
flex-wrap: wrap;
margin-top: 8px;
margin-left: -12px; // Negative button margin
margin-left: -12px;
.btn {
& .btn {
margin: 12px;
text-decoration: none;
padding: 8px 24px;
@ -73,7 +97,7 @@
}
}
.error {
& .error {
margin-top: 8px;
background-color: hsl(224, 52%, 32%);
padding: 12px;

View file

@ -0,0 +1,7 @@
module.exports = {
theme: {
extend: {}
},
variants: {},
plugins: []
}

View file

@ -2,28 +2,20 @@
"compilerOptions": {
"moduleResolution": "node",
"module": "esnext",
"target": "esnext",
"sourceMap": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "esnext",
"strict": true,
"strict": false,
"lib": [
"es2017",
"esnext",
"dom"
],
"baseUrl": "./",
"baseUrl": ".",
"resolveJsonModule": true,
"esModuleInterop": true,
"outDir": "./dist"
},
"include": [
"src/**/*.ts",
"src/**/*.vue"
],
"files": [
"ts-vue-shim.d.ts"
]
"allowJs": true
}
}

169
dashboard/webpack.config.js Normal file
View file

@ -0,0 +1,169 @@
require('dotenv').config();
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const DotenvPlugin = require('dotenv-webpack');
const merge = require('webpack-merge');
const targetDir = path.normalize(path.join(__dirname, 'dist'));
if (! process.env.NODE_ENV) {
console.error('Please set NODE_ENV');
process.exit(1);
}
const babelOpts = {
presets: [
'@babel/preset-env',
],
};
let config = {
entry: './src/main.ts',
output: {
filename: '[name].[hash].js',
path: targetDir,
publicPath: '/',
},
module: {
rules: [
// Vue / Babel / Typescript
{
test: /\.vue$/,
use: ["vue-loader"],
},
{
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",
plugins: loader => {
const plugins = [
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") {
plugins.push(
require('postcss-preset-env')(),
require('cssnano')(),
);
}
return plugins;
},
},
},
],
},
// Images/files
{
test: /\.(png|jpg)$/i,
use: ["file-loader"],
},
// HTML
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: {
root: path.resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href'],
...(process.env.NODE_ENV === 'production' && {
minimize: true,
removeComments: true,
collapseWhitespace: true,
}),
},
},
],
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html',
files: {
"css": ["./src/style/initial.pcss"],
"js": ["./src/main.ts"],
},
}),
new DotenvPlugin(),
],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.mjs', '.vue'],
},
};
if (process.env.NODE_ENV === 'production') {
config = merge(config, {
mode: 'production',
devtool: 'source-map',
});
} else {
config = merge(config, {
mode: 'development',
devtool: 'eval',
devServer: {
...(process.env.DEV_HOST ? { host: process.env.DEV_HOST } : undefined),
historyApiFallback: true,
port: 1234,
},
});
}
module.exports = config;

View file

@ -117,7 +117,7 @@ export function initAuth(app: express.Express) {
if (req.user && req.user.apiKey) {
res.redirect(`${process.env.DASHBOARD_URL}/login-callback/?apiKey=${req.user.apiKey}`);
} else {
res.redirect(`${process.env.DASHBOARD_URL}/login-callback/?error=noaccess`);
res.redirect(`${process.env.DASHBOARD_URL}/login-callback/?error=noAccess`);
}
},
);