feat: try fixing THE SHITTY WATERFALL
This commit is contained in:
parent
15f296a780
commit
fb0bc57542
|
@ -11,14 +11,14 @@ const clientConfig = {
|
|||
maxFileSize: parseInt(process.env.MAX_SIZE, 10),
|
||||
chunkSize: parseInt(process.env.CHUNK_SIZE, 10),
|
||||
maxLinksPerAlbum: parseInt(process.env.MAX_LINKS_PER_ALBUM, 10),
|
||||
publicMode: process.env.PUBLIC_MODE === 'true' ? true : false,
|
||||
userAccounts: process.env.USER_ACCOUNTS === 'true' ? true : false
|
||||
publicMode: process.env.PUBLIC_MODE === 'true',
|
||||
userAccounts: process.env.USER_ACCOUNTS === 'true',
|
||||
};
|
||||
|
||||
export default {
|
||||
mode: 'spa',
|
||||
server: {
|
||||
port: process.env.WEBSITE_PORT
|
||||
port: process.env.WEBSITE_PORT,
|
||||
},
|
||||
srcDir: 'src/site/',
|
||||
head: {
|
||||
|
@ -34,7 +34,7 @@ export default {
|
|||
{
|
||||
hid: 'apple-mobile-web-app-title',
|
||||
name: 'apple-mobile-web-app-title',
|
||||
content: `${process.env.SERVICE_NAME}`
|
||||
content: `${process.env.SERVICE_NAME}`,
|
||||
},
|
||||
{ hid: 'application-name', name: 'application-name', content: `${process.env.SERVICE_NAME}` },
|
||||
// { hid: 'msapplication-config', name: 'msapplication-config', content: `${process.env.DOMAIN}/browserconfig.xml` },
|
||||
|
@ -50,14 +50,14 @@ export default {
|
|||
{ hid: 'og:description', property: 'og:description', content: `${process.env.META_DESCRIPTION}` },
|
||||
{ hid: 'og:image', property: 'og:image', content: `${process.env.DOMAIN}/share.jpg` },
|
||||
{ hid: 'og:image:secure_url', property: 'og:image:secure_url', content: `${process.env.DOMAIN}/share.jpg` },
|
||||
{ hid: 'og:site_name', property: 'og:site_name', content: `${process.env.SERVICE_NAME}` }
|
||||
{ hid: 'og:site_name', property: 'og:site_name', content: `${process.env.SERVICE_NAME}` },
|
||||
],
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Nunito:300,400,600,700' },
|
||||
|
||||
// This one is a pain in the ass to make it customizable, so you should edit it manually
|
||||
{ type: 'application/json+oembed', href: `${process.env.DOMAIN}/oembed.json` }
|
||||
]
|
||||
{ type: 'application/json+oembed', href: `${process.env.DOMAIN}/oembed.json` },
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
'~/plugins/axios',
|
||||
|
@ -67,19 +67,20 @@ export default {
|
|||
'~/plugins/vue-timeago',
|
||||
'~/plugins/flexsearch',
|
||||
'~/plugins/vuebar',
|
||||
'~/plugins/nuxt-client-init'
|
||||
'~/plugins/nuxt-client-init',
|
||||
'~/plugins/notifier',
|
||||
],
|
||||
css: [],
|
||||
modules: ['@nuxtjs/axios', 'cookie-universal-nuxt'],
|
||||
axios: {
|
||||
baseURL: `${process.env.DOMAIN}${process.env.ROUTE_PREFIX}`
|
||||
baseURL: `${process.env.DOMAIN}${process.env.ROUTE_PREFIX}`,
|
||||
},
|
||||
build: {
|
||||
extractCSS: true,
|
||||
postcss: {
|
||||
preset: {
|
||||
autoprefixer
|
||||
}
|
||||
autoprefixer,
|
||||
},
|
||||
},
|
||||
extend(config, { isClient, isDev }) {
|
||||
// Extend only webpack config for client-bundle
|
||||
|
@ -89,6 +90,6 @@ export default {
|
|||
if (isDev) {
|
||||
config.devtool = isClient ? 'source-map' : 'inline-source-map';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -41,6 +41,7 @@ class albumGET extends Route {
|
|||
count = files.length;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (let file of files) {
|
||||
file = Util.constructFilePublicLink(file);
|
||||
}
|
||||
|
@ -49,7 +50,7 @@ class albumGET extends Route {
|
|||
message: 'Successfully retrieved album',
|
||||
name: album.name,
|
||||
files,
|
||||
count
|
||||
count,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ class albumGET extends Route {
|
|||
.orderBy('files.id', 'desc');
|
||||
|
||||
// Create the links for each file
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (let file of files) {
|
||||
file = Util.constructFilePublicLink(file);
|
||||
}
|
||||
|
|
49
src/setup.js
49
src/setup.js
|
@ -1,9 +1,11 @@
|
|||
/* eslint-disable no-console */
|
||||
const randomstring = require('randomstring');
|
||||
const jetpack = require('fs-jetpack');
|
||||
const qoa = require('qoa');
|
||||
|
||||
qoa.config({
|
||||
prefix: '>',
|
||||
underlineQuery: false
|
||||
underlineQuery: false,
|
||||
});
|
||||
|
||||
async function start() {
|
||||
|
@ -15,82 +17,82 @@ async function start() {
|
|||
{
|
||||
type: 'input',
|
||||
query: 'Port to run the API in:',
|
||||
handle: 'SERVER_PORT'
|
||||
handle: 'SERVER_PORT',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Port to run the Website in when in dev mode:',
|
||||
handle: 'WEBSITE_PORT'
|
||||
handle: 'WEBSITE_PORT',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Full domain this instance is gonna be running on (Ex: https://lolisafe.moe):',
|
||||
handle: 'DOMAIN'
|
||||
handle: 'DOMAIN',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Name of the service? (Ex: lolisafe):',
|
||||
handle: 'SERVICE_NAME'
|
||||
handle: 'SERVICE_NAME',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Maximum allowed upload file size in MB (Ex: 100):',
|
||||
handle: 'MAX_SIZE'
|
||||
handle: 'MAX_SIZE',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
query: 'Generate thumbnails for images/videos? (Requires ffmpeg installed and in your PATH)',
|
||||
handle: 'GENERATE_THUMBNAILS',
|
||||
accept: 'y',
|
||||
deny: 'n'
|
||||
deny: 'n',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
query: 'Allow users to download entire albums in ZIP format?',
|
||||
handle: 'GENERATE_ZIPS',
|
||||
accept: 'y',
|
||||
deny: 'n'
|
||||
deny: 'n',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
query: 'Serve files with node?',
|
||||
handle: 'SERVE_WITH_NODE',
|
||||
accept: 'y',
|
||||
deny: 'n'
|
||||
deny: 'n',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Base number of characters for generated file URLs (12 should be good enough):',
|
||||
handle: 'GENERATED_FILENAME_LENGTH'
|
||||
handle: 'GENERATED_FILENAME_LENGTH',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Base number of characters for generated album URLs (6 should be enough):',
|
||||
handle: 'GENERATED_ALBUM_LENGTH'
|
||||
handle: 'GENERATED_ALBUM_LENGTH',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
query: 'Run lolisafe in public mode? (People will be able to upload without an account)',
|
||||
handle: 'PUBLIC_MODE',
|
||||
accept: 'y',
|
||||
deny: 'n'
|
||||
deny: 'n',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
query: 'Enable user signup for new accounts?',
|
||||
handle: 'USER_ACCOUNTS',
|
||||
accept: 'y',
|
||||
deny: 'n'
|
||||
deny: 'n',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Name of the admin account?',
|
||||
handle: 'ADMIN_ACCOUNT'
|
||||
handle: 'ADMIN_ACCOUNT',
|
||||
},
|
||||
{
|
||||
type: 'secure',
|
||||
query: 'Type a secure password for the admin account:',
|
||||
handle: 'ADMIN_PASSWORD'
|
||||
handle: 'ADMIN_PASSWORD',
|
||||
},
|
||||
{
|
||||
type: 'interactive',
|
||||
|
@ -100,29 +102,29 @@ async function start() {
|
|||
menu: [
|
||||
'sqlite3',
|
||||
'pg',
|
||||
'mysql'
|
||||
]
|
||||
'mysql',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Database host (Ignore if you selected sqlite3):',
|
||||
handle: 'DB_HOST'
|
||||
handle: 'DB_HOST',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Database user (Ignore if you selected sqlite3):',
|
||||
handle: 'DB_USER'
|
||||
handle: 'DB_USER',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Database password (Ignore if you selected sqlite3):',
|
||||
handle: 'DB_PASSWORD'
|
||||
handle: 'DB_PASSWORD',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
query: 'Database name (Ignore if you selected sqlite3):',
|
||||
handle: 'DB_DATABASE'
|
||||
}
|
||||
handle: 'DB_DATABASE',
|
||||
},
|
||||
];
|
||||
|
||||
const response = await qoa.prompt(wizard);
|
||||
|
@ -140,12 +142,13 @@ async function start() {
|
|||
META_THEME_COLOR: '#20222b',
|
||||
META_DESCRIPTION: 'Blazing fast file uploader and bunker written in node! 🚀',
|
||||
META_KEYWORDS: 'lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free',
|
||||
META_TWITTER_HANDLE: '@its_pitu'
|
||||
META_TWITTER_HANDLE: '@its_pitu',
|
||||
};
|
||||
|
||||
const allSettings = Object.assign(defaultSettings, response);
|
||||
|
||||
const keys = Object.keys(allSettings);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const item of keys) {
|
||||
envfile += `${item}=${allSettings[item]}\n`;
|
||||
}
|
||||
|
|
|
@ -24,12 +24,10 @@
|
|||
<Waterfall
|
||||
v-if="showWaterfall"
|
||||
:gutterWidth="10"
|
||||
:gutterHeight="4">
|
||||
<WaterfallItem
|
||||
v-for="(item, index) in gridFiles"
|
||||
:key="item.id"
|
||||
:width="width"
|
||||
move-class="item-move">
|
||||
:gutterHeight="4"
|
||||
:itemWidth="width"
|
||||
:items="gridFiles">
|
||||
<template v-slot="{item}">
|
||||
<template v-if="isPublic">
|
||||
<a
|
||||
:href="`${item.url}`"
|
||||
|
@ -81,7 +79,7 @@
|
|||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Delete" position="is-top">
|
||||
<a class="btn" @click="deleteFile(item, index)">
|
||||
<a class="btn" @click="deleteFile(item)">
|
||||
<i class="icon-editorial-trash-a-l" />
|
||||
</a>
|
||||
</b-tooltip>
|
||||
|
@ -92,7 +90,7 @@
|
|||
</b-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</WaterfallItem>
|
||||
</template>
|
||||
</Waterfall>
|
||||
</template>
|
||||
<div v-else>
|
||||
|
@ -185,12 +183,10 @@
|
|||
import { mapState } from 'vuex';
|
||||
|
||||
import Waterfall from './waterfall/Waterfall.vue';
|
||||
import WaterfallItem from './waterfall/WaterfallItem.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Waterfall,
|
||||
WaterfallItem,
|
||||
},
|
||||
props: {
|
||||
files: {
|
||||
|
@ -253,13 +249,13 @@ export default {
|
|||
const data = await this.$search.do(this.searchTerm, ['name', 'original', 'type', 'albums:name']);
|
||||
console.log('> Search result data', data);
|
||||
},
|
||||
deleteFile(file, index) {
|
||||
this.$buefy.dialog.confirm({
|
||||
deleteFile(file) {
|
||||
this.$emit('delete', file);
|
||||
/* this.$buefy.dialog.confirm({
|
||||
title: 'Deleting file',
|
||||
message: 'Are you sure you want to <b>delete</b> this file?',
|
||||
confirmText: 'Delete File',
|
||||
type: 'is-danger',
|
||||
hasIcon: true,
|
||||
onConfirm: async () => {
|
||||
const response = await this.$axios.$delete(`file/${file.id}`);
|
||||
if (this.showList) {
|
||||
|
@ -274,7 +270,7 @@ export default {
|
|||
}
|
||||
return this.$buefy.toast.open(response.message);
|
||||
},
|
||||
});
|
||||
}); */
|
||||
},
|
||||
isAlbumSelected(id) {
|
||||
if (!this.showingModalForFile) return false;
|
||||
|
@ -311,10 +307,6 @@ export default {
|
|||
const foundIndex = this.hoveredItems.indexOf(id);
|
||||
if (foundIndex > -1) return;
|
||||
this.hoveredItems.push(id);
|
||||
/// XXX: THIS IS NOT OK!
|
||||
this.$nextTick(() => {
|
||||
this.$refs.video.forEach((e) => e.play().catch(() => {}));
|
||||
});
|
||||
},
|
||||
mouseOut(id) {
|
||||
console.log('out', id);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<style>
|
||||
.waterfall {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="waterfall">
|
||||
<slot />
|
||||
<WaterfallItem v-for="(item, index) in items" :key="item.id" :ref="`item-${item.id}`" :width="itemWidth">
|
||||
<slot :item="item" />
|
||||
</WaterfallItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallItem from './WaterfallItem.vue';
|
||||
|
||||
const quickSort = (arr, type) => {
|
||||
const left = [];
|
||||
const right = [];
|
||||
|
@ -49,6 +49,9 @@ const sum = (arr) => arr.reduce((acc, val) => acc + val, 0);
|
|||
|
||||
export default {
|
||||
name: 'Waterfall',
|
||||
components: {
|
||||
WaterfallItem,
|
||||
},
|
||||
props: {
|
||||
gutterWidth: {
|
||||
type: Number,
|
||||
|
@ -82,6 +85,14 @@ export default {
|
|||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
itemWidth: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -89,15 +100,21 @@ export default {
|
|||
colNum: 0,
|
||||
lastWidth: 0,
|
||||
percentWidthArr: [],
|
||||
readyChildCount: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
items() {
|
||||
this.$nextTick(() => this.render('watch'));
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$on('itemRender', () => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.timer = setTimeout(() => {
|
||||
this.render();
|
||||
this.render('created');
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
@ -105,6 +122,10 @@ export default {
|
|||
this.resizeHandle();
|
||||
this.$watch('resizable', this.resizeHandle);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$off('itemRender');
|
||||
_.off(window, 'resize', this.render);
|
||||
},
|
||||
methods: {
|
||||
calulate(arr) {
|
||||
const pageWidth = this.fixWidth ? this.fixWidth : this.$el.offsetWidth;
|
||||
|
@ -135,10 +156,12 @@ export default {
|
|||
_.off(window, 'resize', this.render, false);
|
||||
}
|
||||
},
|
||||
render() {
|
||||
render(context) {
|
||||
console.log(context);
|
||||
if (!this.items) return;
|
||||
// 重新排序
|
||||
let childArr = [];
|
||||
childArr = this.$children.map((child) => child.getMeta());
|
||||
childArr = this.items.map(({ id }) => this.$refs[`item-${id}`][0].getMeta());
|
||||
childArr = quickSort(childArr, 'order');
|
||||
// 计算列数
|
||||
this.calulate(childArr[0]);
|
||||
|
@ -180,3 +203,9 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.waterfall {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
<style scoped>
|
||||
.waterfall-item {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="waterfall-item">
|
||||
<slot />
|
||||
|
@ -80,3 +74,9 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.waterfall-item {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -20,15 +20,21 @@
|
|||
<template v-if="files && files.length">
|
||||
<div class="align-top">
|
||||
<div class="container">
|
||||
<h1 class="title">{{ name }}</h1>
|
||||
<h2 class="subtitle">Serving {{ files ? files.length : 0 }} files</h2>
|
||||
<a v-if="downloadLink"
|
||||
<h1 class="title">
|
||||
{{ name }}
|
||||
</h1>
|
||||
<h2 class="subtitle">
|
||||
Serving {{ files ? files.length : 0 }} files
|
||||
</h2>
|
||||
<a
|
||||
v-if="downloadLink"
|
||||
:href="downloadLink">Download Album</a>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<Grid v-if="files && files.length"
|
||||
<Grid
|
||||
v-if="files && files.length"
|
||||
:files="files"
|
||||
:isPublic="true"
|
||||
:width="200"
|
||||
|
@ -38,16 +44,20 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<div class="container">
|
||||
<h1 class="title">:(</h1>
|
||||
<h2 class="subtitle">This album seems to be empty</h2>
|
||||
<h1 class="title">
|
||||
:(
|
||||
</h1>
|
||||
<h2 class="subtitle">
|
||||
This album seems to be empty
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Grid from '~/components/grid/Grid.vue';
|
||||
import axios from 'axios';
|
||||
import Grid from '~/components/grid/Grid.vue';
|
||||
|
||||
export default {
|
||||
components: { Grid },
|
||||
|
@ -57,7 +67,7 @@ export default {
|
|||
computed: {
|
||||
config() {
|
||||
return this.$store.state.config;
|
||||
}
|
||||
},
|
||||
},
|
||||
async asyncData({ app, params, error }) {
|
||||
try {
|
||||
|
@ -67,7 +77,7 @@ export default {
|
|||
name: data.name,
|
||||
downloadEnabled: data.downloadEnabled,
|
||||
files: data.files,
|
||||
downloadLink
|
||||
downloadLink,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log('Error when retrieving album', err);
|
||||
|
@ -90,8 +100,8 @@ export default {
|
|||
{ vmid: 'og:title', property: 'og:title', content: `Album: ${this.name} | Files: ${this.files.length}` },
|
||||
{ vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
|
||||
{ vmid: 'og:image', property: 'og:image', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` },
|
||||
{ vmid: 'og:image:secure_url', property: 'og:image:secure_url', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` }
|
||||
]
|
||||
{ vmid: 'og:image:secure_url', property: 'og:image:secure_url', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` },
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
@ -103,9 +113,9 @@ export default {
|
|||
{ vmid: 'twitter:description', name: 'twitter:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
|
||||
{ vmid: 'og:url', property: 'og:url', content: `${this.config.URL}/a/${this.$route.params.identifier}` },
|
||||
{ vmid: 'og:title', property: 'og:title', content: 'lolisafe' },
|
||||
{ vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' }
|
||||
]
|
||||
{ vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -6,62 +6,82 @@
|
|||
<Sidebar />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="subtitle">Account settings</h2>
|
||||
<h2 class="subtitle">
|
||||
Account settings
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
<b-field label="Username"
|
||||
<b-field
|
||||
label="Username"
|
||||
message="Nothing to do here"
|
||||
horizontal>
|
||||
<b-input :value="user.username"
|
||||
<b-input
|
||||
:value="user.username"
|
||||
expanded
|
||||
disabled />
|
||||
</b-field>
|
||||
|
||||
<b-field label="Current password"
|
||||
<b-field
|
||||
label="Current password"
|
||||
message="If you want to change your password input the current one here"
|
||||
horizontal>
|
||||
<b-input v-model="password"
|
||||
<b-input
|
||||
v-model="password"
|
||||
type="password"
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
<b-field label="New password"
|
||||
<b-field
|
||||
label="New password"
|
||||
message="Your new password"
|
||||
horizontal>
|
||||
<b-input v-model="newPassword"
|
||||
<b-input
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
<b-field label="New password again"
|
||||
<b-field
|
||||
label="New password again"
|
||||
message="Your new password once again"
|
||||
horizontal>
|
||||
<b-input v-model="reNewPassword"
|
||||
<b-input
|
||||
v-model="reNewPassword"
|
||||
type="password"
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
<div class="mb2 mt2 text-center">
|
||||
<button class="button is-primary"
|
||||
@click="changePassword">Change password</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="changePassword">
|
||||
Change password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<b-field label="API key"
|
||||
<b-field
|
||||
label="API key"
|
||||
message="This API key lets you use the service from other apps"
|
||||
horizontal>
|
||||
<b-field expanded>
|
||||
<b-input :value="apiKey"
|
||||
<b-input
|
||||
:value="apiKey"
|
||||
expanded
|
||||
disabled />
|
||||
<p class="control">
|
||||
<button class="button is-primary">Copy</button>
|
||||
<button class="button is-primary">
|
||||
Copy
|
||||
</button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
|
||||
<div class="mb2 mt2 text-center">
|
||||
<button class="button is-primary"
|
||||
@click="promptNewAPIKey">Request new API key</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="promptNewAPIKey">
|
||||
Request new API key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,7 +95,7 @@ import Sidebar from '~/components/sidebar/Sidebar.vue';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar
|
||||
Sidebar,
|
||||
},
|
||||
middleware: ['auth', ({ store }) => {
|
||||
store.dispatch('auth/fetchCurrentUser');
|
||||
|
@ -84,21 +104,21 @@ export default {
|
|||
return {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
reNewPassword: ''
|
||||
reNewPassword: '',
|
||||
};
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ 'apiKey': 'auth/getApiKey' }),
|
||||
...mapState({
|
||||
user: state => state.auth.user
|
||||
})
|
||||
user: (state) => state.auth.user,
|
||||
}),
|
||||
},
|
||||
metaInfo() {
|
||||
return { title: 'Account' };
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
getUserSetttings: 'auth/fetchCurrentUser'
|
||||
getUserSetttings: 'auth/fetchCurrentUser',
|
||||
}),
|
||||
async changePassword() {
|
||||
const { password, newPassword, reNewPassword } = this;
|
||||
|
@ -106,21 +126,21 @@ export default {
|
|||
if (!password || !newPassword || !reNewPassword) {
|
||||
this.$store.dispatch('alert/set', {
|
||||
text: 'One or more fields are missing',
|
||||
error: true
|
||||
error: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (newPassword !== reNewPassword) {
|
||||
this.$store.dispatch('alert/set', {
|
||||
text: 'Passwords don\'t match',
|
||||
error: true
|
||||
error: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.$store.dispatch('auth/changePassword', {
|
||||
password,
|
||||
newPassword
|
||||
newPassword,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
|
@ -131,13 +151,13 @@ export default {
|
|||
this.$buefy.dialog.confirm({
|
||||
type: 'is-danger',
|
||||
message: 'Are you sure you want to regenerate your API key? Previously generated API keys will stop working. Make sure to write the new key down as this is the only time it will be displayed to you.',
|
||||
onConfirm: () => this.requestNewAPIKey()
|
||||
onConfirm: () => this.requestNewAPIKey(),
|
||||
});
|
||||
},
|
||||
async requestNewAPIKey() {
|
||||
const response = await this.$store.dispatch('auth/requestAPIKey');
|
||||
const response = await this.$store.dispatch('auth/requestAPIKey');
|
||||
this.$buefy.toast.open(response.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -9,40 +9,51 @@
|
|||
<Sidebar />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="subtitle">User details</h2>
|
||||
<h2 class="subtitle">
|
||||
User details
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
<b-field label="User Id"
|
||||
<b-field
|
||||
label="User Id"
|
||||
horizontal>
|
||||
<span>{{ user.id }}</span>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Username"
|
||||
<b-field
|
||||
label="Username"
|
||||
horizontal>
|
||||
<span>{{ user.username }}</span>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Enabled"
|
||||
<b-field
|
||||
label="Enabled"
|
||||
horizontal>
|
||||
<span>{{ user.enabled }}</span>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Registered"
|
||||
<b-field
|
||||
label="Registered"
|
||||
horizontal>
|
||||
<span><timeago :since="user.createdAt" /></span>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Files"
|
||||
<b-field
|
||||
label="Files"
|
||||
horizontal>
|
||||
<span>{{ files.length }}</span>
|
||||
</b-field>
|
||||
|
||||
<div class="mb2 mt2 text-center">
|
||||
<button class="button is-danger"
|
||||
@click="promptDisableUser">Disable user</button>
|
||||
<button
|
||||
class="button is-danger"
|
||||
@click="promptDisableUser">
|
||||
Disable user
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Grid v-if="files.length"
|
||||
<Grid
|
||||
v-if="files.length"
|
||||
:files="files" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,14 +68,14 @@ import Grid from '~/components/grid/Grid.vue';
|
|||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
Grid
|
||||
Grid,
|
||||
},
|
||||
middleware: ['auth', 'admin'],
|
||||
data() {
|
||||
return {
|
||||
options: {},
|
||||
files: null,
|
||||
user: null
|
||||
user: null,
|
||||
};
|
||||
},
|
||||
async asyncData({ $axios, route }) {
|
||||
|
@ -72,13 +83,13 @@ export default {
|
|||
const response = await $axios.$get(`/admin/users/${route.params.id}`);
|
||||
return {
|
||||
files: response.files ? response.files : null,
|
||||
user: response.user ? response.user : null
|
||||
user: response.user ? response.user : null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
files: null,
|
||||
user: null
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
@ -87,15 +98,15 @@ export default {
|
|||
this.$buefy.dialog.confirm({
|
||||
type: 'is-danger',
|
||||
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
|
||||
onConfirm: () => this.disableUser()
|
||||
onConfirm: () => this.disableUser(),
|
||||
});
|
||||
},
|
||||
async disableUser() {
|
||||
const response = await this.$axios.$post('admin/users/disable', {
|
||||
id: this.user.id
|
||||
id: this.user.id,
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,143 @@
|
|||
<template>
|
||||
<section class="section is-fullheight dashboard">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="subtitle">
|
||||
Manage your users
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
<div class="view-container">
|
||||
<b-table
|
||||
:data="users || []"
|
||||
:mobile-cards="true">
|
||||
<template slot-scope="props">
|
||||
<b-table-column
|
||||
field="id"
|
||||
label="Id"
|
||||
centered>
|
||||
{{ props.row.id }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column
|
||||
field="username"
|
||||
label="Username"
|
||||
centered>
|
||||
<nuxt-link :to="`/dashboard/admin/user/${props.row.id}`">
|
||||
{{ props.row.username }}
|
||||
</nuxt-link>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column
|
||||
field="enabled"
|
||||
label="Enabled"
|
||||
centered>
|
||||
<b-switch
|
||||
v-model="props.row.enabled"
|
||||
@input="changeEnabledStatus(props.row)" />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column
|
||||
field="isAdmin"
|
||||
label="Admin"
|
||||
centered>
|
||||
<b-switch
|
||||
v-model="props.row.isAdmin"
|
||||
@input="changeIsAdmin(props.row)" />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column
|
||||
field="purge"
|
||||
centered>
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="promptPurgeFiles(props.row)">
|
||||
Purge files
|
||||
</button>
|
||||
</b-table-column>
|
||||
</template>
|
||||
<template slot="empty">
|
||||
<div class="has-text-centered">
|
||||
<i class="icon-misc-mood-sad" />
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
Nothing here
|
||||
</div>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<div class="has-text-right">
|
||||
{{ users.length }} users
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from '~/components/sidebar/Sidebar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
},
|
||||
middleware: ['auth', 'admin'],
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
config() {
|
||||
return this.$store.state.config;
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return { title: 'Uploads' };
|
||||
},
|
||||
mounted() {
|
||||
this.getUsers();
|
||||
},
|
||||
methods: {
|
||||
async getUsers() {
|
||||
const response = await this.$axios.$get('admin/users');
|
||||
this.users = response.users;
|
||||
},
|
||||
async changeEnabledStatus(row) {
|
||||
const response = await this.$axios.$post(`admin/users/${row.enabled ? 'enable' : 'disable'}`, {
|
||||
id: row.id,
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
},
|
||||
async changeIsAdmin(row) {
|
||||
const response = await this.$axios.$post(`admin/users/${row.isAdmin ? 'promote' : 'demote'}`, {
|
||||
id: row.id,
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
},
|
||||
promptPurgeFiles(row) {
|
||||
this.$buefy.dialog.confirm({
|
||||
message: 'Are you sure you want to delete this user\'s files?',
|
||||
onConfirm: () => this.purgeFiles(row),
|
||||
});
|
||||
},
|
||||
async purgeFiles(row) {
|
||||
const response = await this.$axios.$post('admin/users/purge', {
|
||||
id: row.id,
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~/assets/styles/_colors.scss';
|
||||
div.view-container {
|
||||
|
@ -107,9 +247,6 @@
|
|||
}
|
||||
|
||||
div.column > h2.subtitle { padding-top: 1px; }
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@import '~/assets/styles/_colors.scss';
|
||||
|
||||
.b-table {
|
||||
.table-wrapper {
|
||||
|
@ -118,130 +255,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<template>
|
||||
<section class="section is-fullheight dashboard">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="subtitle">Manage your users</h2>
|
||||
<hr>
|
||||
|
||||
<div class="view-container">
|
||||
<b-table
|
||||
:data="users || []"
|
||||
:mobile-cards="true">
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="id"
|
||||
label="Id"
|
||||
centered>
|
||||
{{ props.row.id }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="username"
|
||||
label="Username"
|
||||
centered>
|
||||
<nuxt-link :to="`/dashboard/admin/user/${props.row.id}`">{{ props.row.username }}</nuxt-link>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="enabled"
|
||||
label="Enabled"
|
||||
centered>
|
||||
<b-switch v-model="props.row.enabled"
|
||||
@input="changeEnabledStatus(props.row)" />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="isAdmin"
|
||||
label="Admin"
|
||||
centered>
|
||||
<b-switch v-model="props.row.isAdmin"
|
||||
@input="changeIsAdmin(props.row)" />
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="purge"
|
||||
centered>
|
||||
<button class="button is-primary"
|
||||
@click="promptPurgeFiles(props.row)">Purge files</button>
|
||||
</b-table-column>
|
||||
</template>
|
||||
<template slot="empty">
|
||||
<div class="has-text-centered">
|
||||
<i class="icon-misc-mood-sad" />
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
Nothing here
|
||||
</div>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<div class="has-text-right">
|
||||
{{ users.length }} users
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from '~/components/sidebar/Sidebar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar
|
||||
},
|
||||
middleware: ['auth', 'admin'],
|
||||
data() {
|
||||
return {
|
||||
users: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
config() {
|
||||
return this.$store.state.config;
|
||||
}
|
||||
},
|
||||
metaInfo() {
|
||||
return { title: 'Uploads' };
|
||||
},
|
||||
mounted() {
|
||||
this.getUsers();
|
||||
},
|
||||
methods: {
|
||||
async getUsers() {
|
||||
const response = await this.$axios.$get(`admin/users`);
|
||||
this.users = response.users;
|
||||
},
|
||||
async changeEnabledStatus(row) {
|
||||
const response = await this.$axios.$post(`admin/users/${row.enabled ? 'enable' : 'disable'}`, {
|
||||
id: row.id
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
},
|
||||
async changeIsAdmin(row) {
|
||||
const response = await this.$axios.$post(`admin/users/${row.isAdmin ? 'promote' : 'demote'}`, {
|
||||
id: row.id
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
},
|
||||
promptPurgeFiles(row) {
|
||||
this.$buefy.dialog.confirm({
|
||||
message: 'Are you sure you want to delete this user\'s files?',
|
||||
onConfirm: () => this.purgeFiles(row)
|
||||
});
|
||||
},
|
||||
async purgeFiles(row) {
|
||||
const response = await this.$axios.$post(`admin/users/purge`, {
|
||||
id: row.id
|
||||
});
|
||||
this.$buefy.toast.open(response.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h1 class="title is-3">
|
||||
{{ name }}
|
||||
{{ images.name }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
|
@ -45,7 +45,7 @@
|
|||
|
||||
<Grid
|
||||
v-if="totalFiles"
|
||||
:files="album.files"
|
||||
:files="images.files"
|
||||
:total="totalFiles">
|
||||
<template v-slot:pagination>
|
||||
<b-pagination
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
Grid,
|
||||
},
|
||||
middleware: ['auth', ({ route, store }) => {
|
||||
store.dispatch('album/fetchById', { id: route.params.id });
|
||||
store.dispatch('images/fetchByAlbumId', { id: route.params.id });
|
||||
}],
|
||||
data() {
|
||||
return {
|
||||
|
@ -92,12 +92,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
totalFiles: 'album/getTotalFiles',
|
||||
shouldPaginate: 'album/shouldPaginate',
|
||||
limit: 'album/getLimit',
|
||||
name: 'album/getName',
|
||||
totalFiles: 'images/getTotalFiles',
|
||||
shouldPaginate: 'images/shouldPaginate',
|
||||
limit: 'images/getLimit',
|
||||
}),
|
||||
...mapState(['album']),
|
||||
...mapState(['images']),
|
||||
id() {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
|
@ -110,7 +109,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
fetch: 'album/fetchById',
|
||||
fetch: 'images/fetchByAlbumId',
|
||||
}),
|
||||
fetchPaginate() {
|
||||
this.fetch({ id: this.id, page: this.current });
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<nav class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h2 class="subtitle">Your uploaded files</h2>
|
||||
<h2 class="subtitle">
|
||||
Your uploaded files
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
|
@ -17,7 +19,7 @@
|
|||
<b-field>
|
||||
<b-input
|
||||
placeholder="Search"
|
||||
type="search"/>
|
||||
type="search" />
|
||||
<p class="control">
|
||||
<button
|
||||
outlined
|
||||
|
@ -28,15 +30,17 @@
|
|||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
<hr>
|
||||
|
||||
<b-loading :active="images.isLoading" />
|
||||
|
||||
<Grid v-if="totalFiles"
|
||||
<Grid
|
||||
v-if="totalFiles && !isLoading"
|
||||
:files="images.files"
|
||||
:enableSearch="false"
|
||||
class="grid">
|
||||
class="grid"
|
||||
@delete="handleFileDelete">
|
||||
<template v-slot:pagination>
|
||||
<b-pagination
|
||||
v-if="shouldPaginate"
|
||||
|
@ -70,38 +74,44 @@ import Grid from '~/components/grid/Grid.vue';
|
|||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
Grid
|
||||
Grid,
|
||||
},
|
||||
middleware: ['auth', ({ store }) => {
|
||||
store.dispatch('images/fetch');
|
||||
}],
|
||||
data() {
|
||||
return {
|
||||
current: 1
|
||||
current: 1,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
...mapGetters({
|
||||
totalFiles: 'images/getTotalFiles',
|
||||
shouldPaginate: 'images/shouldPaginate',
|
||||
limit: 'images/getLimit'
|
||||
limit: 'images/getLimit',
|
||||
}),
|
||||
...mapState(['images'])
|
||||
...mapState(['images']),
|
||||
},
|
||||
metaInfo() {
|
||||
return { title: 'Uploads' };
|
||||
},
|
||||
watch: {
|
||||
current: 'fetchPaginate'
|
||||
current: 'fetchPaginate',
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
fetch: 'images/fetch'
|
||||
fetch: 'images/fetch',
|
||||
}),
|
||||
fetchPaginate() {
|
||||
this.fetch(this.current);
|
||||
}
|
||||
}
|
||||
async fetchPaginate() {
|
||||
this.isLoading = true;
|
||||
await this.fetch(this.current);
|
||||
this.isLoading = false;
|
||||
},
|
||||
handleFileDelete(file) {
|
||||
console.log('yep!', file.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -10,13 +10,15 @@
|
|||
<div class="columns">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<b-field>
|
||||
<b-input v-model="username"
|
||||
<b-input
|
||||
v-model="username"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
@keyup.enter.native="login" />
|
||||
</b-field>
|
||||
<b-field>
|
||||
<b-input v-model="password"
|
||||
<b-input
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
password-reveal
|
||||
|
@ -24,12 +26,18 @@
|
|||
</b-field>
|
||||
|
||||
<p class="control has-addons is-pulled-right">
|
||||
<router-link v-if="config.userAccounts"
|
||||
<router-link
|
||||
v-if="config.userAccounts"
|
||||
to="/register"
|
||||
class="is-text">Don't have an account?</router-link>
|
||||
class="is-text">
|
||||
Don't have an account?
|
||||
</router-link>
|
||||
<span v-else>Registration is closed at the moment</span>
|
||||
<button class="button is-primary big ml1"
|
||||
@click="login">login</button>
|
||||
<button
|
||||
class="button is-primary big ml1"
|
||||
@click="login">
|
||||
login
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,7 +81,7 @@ export default {
|
|||
password: null,
|
||||
mfaCode: null,
|
||||
isMfaModalActive: false,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: mapState(['config', 'auth']),
|
||||
|
@ -87,21 +95,27 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async login() {
|
||||
if (this.auth.isLoading) return;
|
||||
if (this.isLoading) return;
|
||||
|
||||
const { username, password } = this;
|
||||
if (!username || !password) {
|
||||
this.$store.dispatch('alert/set', {
|
||||
text: 'Please fill both fields before attempting to log in.',
|
||||
error: true
|
||||
error: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('auth/login', { username, password });
|
||||
|
||||
if (this.auth.loggedIn) {
|
||||
this.redirect();
|
||||
try {
|
||||
this.isLoading = true;
|
||||
await this.$store.dispatch('auth/login', { username, password });
|
||||
if (this.auth.loggedIn) {
|
||||
this.redirect();
|
||||
}
|
||||
} catch (e) {
|
||||
this.$store.dispatch('alert/set', { text: e.message, error: true }, { root: true });
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
/*
|
||||
|
@ -118,14 +132,14 @@ export default {
|
|||
this.isLoading = false;
|
||||
this.$onPromiseError(err);
|
||||
});
|
||||
},*/
|
||||
}, */
|
||||
redirect() {
|
||||
if (typeof this.$route.query.redirect !== 'undefined') {
|
||||
this.$router.push(this.$route.query.redirect);
|
||||
return;
|
||||
}
|
||||
this.$router.push('/dashboard');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default ({ store }, inject) => {
|
||||
inject('notifier', {
|
||||
showMessage({ content = '', type = '' }) {
|
||||
store.commit('alert/set', { content, color });
|
||||
}
|
||||
store.commit('alert/set', { content, type });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue