aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZephyrrus <[email protected]>2020-07-07 02:02:59 +0300
committerZephyrrus <[email protected]>2020-07-07 02:02:59 +0300
commitfb0bc57542a44dcc94149f393d8a4ff0c2e7902b (patch)
treec80b075e1d53a1c381a9f40195a1fd72c7b69922 /src
parentchore: eslint stores (diff)
downloadhost.fuwn.me-fb0bc57542a44dcc94149f393d8a4ff0c2e7902b.tar.xz
host.fuwn.me-fb0bc57542a44dcc94149f393d8a4ff0c2e7902b.zip
feat: try fixing THE SHITTY WATERFALL
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/albums/albumFullGET.js3
-rw-r--r--src/api/routes/albums/albumGET.js1
-rw-r--r--src/setup.js49
-rw-r--r--src/site/components/grid/Grid.vue28
-rw-r--r--src/site/components/grid/waterfall/Waterfall.vue47
-rw-r--r--src/site/components/grid/waterfall/WaterfallItem.vue12
-rw-r--r--src/site/components/imageInfo/ImageInfo.vue0
-rw-r--r--src/site/pages/a/_identifier.vue38
-rw-r--r--src/site/pages/dashboard/account.vue78
-rw-r--r--src/site/pages/dashboard/admin/user/_id.vue45
-rw-r--r--src/site/pages/dashboard/admin/users.vue270
-rw-r--r--src/site/pages/dashboard/albums/_id.vue17
-rw-r--r--src/site/pages/dashboard/index.vue42
-rw-r--r--src/site/pages/login.vue46
-rw-r--r--src/site/plugins/notifier.js4
15 files changed, 390 insertions, 290 deletions
diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js
index a5c9707..2c3a790 100644
--- a/src/api/routes/albums/albumFullGET.js
+++ b/src/api/routes/albums/albumFullGET.js
@@ -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,
});
}
}
diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js
index f40750b..81edc95 100644
--- a/src/api/routes/albums/albumGET.js
+++ b/src/api/routes/albums/albumGET.js
@@ -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);
}
diff --git a/src/setup.js b/src/setup.js
index 080320f..91eaa1c 100644
--- a/src/setup.js
+++ b/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`;
}
diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue
index d4fe067..e6a8c64 100644
--- a/src/site/components/grid/Grid.vue
+++ b/src/site/components/grid/Grid.vue
@@ -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);
diff --git a/src/site/components/grid/waterfall/Waterfall.vue b/src/site/components/grid/waterfall/Waterfall.vue
index cccc3ac..a83a275 100644
--- a/src/site/components/grid/waterfall/Waterfall.vue
+++ b/src/site/components/grid/waterfall/Waterfall.vue
@@ -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>
diff --git a/src/site/components/grid/waterfall/WaterfallItem.vue b/src/site/components/grid/waterfall/WaterfallItem.vue
index 2a5c69a..a49c58c 100644
--- a/src/site/components/grid/waterfall/WaterfallItem.vue
+++ b/src/site/components/grid/waterfall/WaterfallItem.vue
@@ -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>
diff --git a/src/site/components/imageInfo/ImageInfo.vue b/src/site/components/imageInfo/ImageInfo.vue
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/site/components/imageInfo/ImageInfo.vue
diff --git a/src/site/pages/a/_identifier.vue b/src/site/pages/a/_identifier.vue
index 24d172b..3746cc6 100644
--- a/src/site/pages/a/_identifier.vue
+++ b/src/site/pages/a/_identifier.vue
@@ -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>
diff --git a/src/site/pages/dashboard/account.vue b/src/site/pages/dashboard/account.vue
index 1c335e8..fb8b273 100644
--- a/src/site/pages/dashboard/account.vue
+++ b/src/site/pages/dashboard/account.vue
@@ -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>
diff --git a/src/site/pages/dashboard/admin/user/_id.vue b/src/site/pages/dashboard/admin/user/_id.vue
index 8c73037..1755b89 100644
--- a/src/site/pages/dashboard/admin/user/_id.vue
+++ b/src/site/pages/dashboard/admin/user/_id.vue
@@ -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>
diff --git a/src/site/pages/dashboard/admin/users.vue b/src/site/pages/dashboard/admin/users.vue
index 695cf0b..269946c 100644
--- a/src/site/pages/dashboard/admin/users.vue
+++ b/src/site/pages/dashboard/admin/users.vue
@@ -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>
diff --git a/src/site/pages/dashboard/albums/_id.vue b/src/site/pages/dashboard/albums/_id.vue
index 0d3bd68..33b0319 100644
--- a/src/site/pages/dashboard/albums/_id.vue
+++ b/src/site/pages/dashboard/albums/_id.vue
@@ -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 });
diff --git a/src/site/pages/dashboard/index.vue b/src/site/pages/dashboard/index.vue
index 89f0fac..71df7eb 100644
--- a/src/site/pages/dashboard/index.vue
+++ b/src/site/pages/dashboard/index.vue
@@ -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>
diff --git a/src/site/pages/login.vue b/src/site/pages/login.vue
index cef0868..9e5658d 100644
--- a/src/site/pages/login.vue
+++ b/src/site/pages/login.vue
@@ -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>
diff --git a/src/site/plugins/notifier.js b/src/site/plugins/notifier.js
index bcf7878..f37b96e 100644
--- a/src/site/plugins/notifier.js
+++ b/src/site/plugins/notifier.js
@@ -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 });
+ },
});
};