refactor: refactor most of the admin pages to use the store instead of internal states

This commit is contained in:
Zephyrrus 2020-07-09 02:24:40 +03:00
parent da703de1d0
commit fd3f6de51a
12 changed files with 263 additions and 136 deletions

View File

@ -52,11 +52,11 @@
</b-field>
<div class="mb2 mt2 text-center">
<button
class="button is-primary"
<b-button
type="is-lolisafe"
@click="changePassword">
Change password
</button>
</b-button>
</div>
<b-field
@ -69,19 +69,21 @@
expanded
disabled />
<p class="control">
<button class="button is-primary">
<b-button
type="is-lolisafe"
@click="copyKey">
Copy
</button>
</b-button>
</p>
</b-field>
</b-field>
<div class="mb2 mt2 text-center">
<button
class="button is-primary"
<b-button
type="is-lolisafe"
@click="promptNewAPIKey">
Request new API key
</button>
</b-button>
</div>
</div>
</div>
@ -154,6 +156,10 @@ export default {
onConfirm: () => this.requestNewAPIKey(),
});
},
copyKey() {
this.$clipboard(this.apiKey);
this.$notifier.success('API key copied to clipboard');
},
async requestNewAPIKey() {
const response = await this.$store.dispatch('auth/requestAPIKey');
this.$buefy.toast.open(response.message);

View File

@ -41,20 +41,27 @@
<b-field
label="Files"
horizontal>
<span>{{ files.length }}</span>
<span>{{ user.files.length }}</span>
</b-field>
<div class="mb2 mt2 text-center">
<button
class="button is-danger"
<b-button
v-if="user.enabled"
type="is-danger"
@click="promptDisableUser">
Disable user
</button>
</b-button>
<b-button
v-if="!user.enabled"
type="is-success"
@click="promptEnableUser">
Enable user
</b-button>
</div>
<Grid
v-if="files.length"
:files="files" />
v-if="user.files.length"
:files="user.files" />
</div>
</div>
</div>
@ -62,6 +69,7 @@
</template>
<script>
import { mapState } from 'vuex';
import Sidebar from '~/components/sidebar/Sidebar.vue';
import Grid from '~/components/grid/Grid.vue';
@ -70,42 +78,42 @@ export default {
Sidebar,
Grid,
},
middleware: ['auth', 'admin'],
middleware: ['auth', 'admin', ({ route, store }) => {
try {
store.dispatch('admin/fetchUser', route.params.id);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
}],
data() {
return {
options: {},
files: null,
user: null,
};
},
async asyncData({ $axios, route }) {
try {
const response = await $axios.$get(`/admin/users/${route.params.id}`);
return {
files: response.files ? response.files : null,
user: response.user ? response.user : null,
};
} catch (error) {
console.error(error);
return {
files: null,
user: null,
};
}
},
computed: mapState({
user: (state) => state.admin.user,
}),
methods: {
promptDisableUser() {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
message: 'Are you sure you want to disable the account of this user?',
onConfirm: () => this.disableUser(),
});
},
async disableUser() {
const response = await this.$axios.$post('admin/users/disable', {
id: this.user.id,
promptEnableUser() {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to enable the account of this user?',
onConfirm: () => this.enableUser(),
});
this.$buefy.toast.open(response.message);
},
disableUser() {
this.$handler.executeAction('admin/disableUser', this.user.id);
},
enableUser() {
this.$handler.executeAction('admin/enableUser', this.user.id);
},
},
};

View File

@ -13,7 +13,7 @@
<div class="view-container">
<b-table
:data="users || []"
:data="users"
:mobile-cards="true">
<template slot-scope="props">
<b-table-column
@ -37,7 +37,7 @@
label="Enabled"
centered>
<b-switch
v-model="props.row.enabled"
:value="props.row.enabled"
@input="changeEnabledStatus(props.row)" />
</b-table-column>
@ -46,18 +46,18 @@
label="Admin"
centered>
<b-switch
v-model="props.row.isAdmin"
:value="props.row.isAdmin"
@input="changeIsAdmin(props.row)" />
</b-table-column>
<b-table-column
field="purge"
centered>
<button
class="button is-primary"
<b-button
type="is-danger"
@click="promptPurgeFiles(props.row)">
Purge files
</button>
</b-button>
</b-table-column>
</template>
<template slot="empty">
@ -82,45 +82,42 @@
</template>
<script>
import { mapState } from 'vuex';
import Sidebar from '~/components/sidebar/Sidebar.vue';
export default {
components: {
Sidebar,
},
middleware: ['auth', 'admin'],
data() {
return {
users: [],
};
},
computed: {
config() {
return this.$store.state.config;
},
},
middleware: ['auth', 'admin', ({ route, store }) => {
try {
store.dispatch('admin/fetchUsers', route.params.id);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
}],
computed: mapState({
users: (state) => state.admin.users,
config: (state) => 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);
if (row.enabled) {
this.$handler.executeAction('admin/disableUser', row.id);
} else {
this.$handler.executeAction('admin/enableUser', row.id);
}
},
async changeIsAdmin(row) {
const response = await this.$axios.$post(`admin/users/${row.isAdmin ? 'promote' : 'demote'}`, {
id: row.id,
});
this.$buefy.toast.open(response.message);
if (row.isAdmin) {
this.$handler.executeAction('admin/demoteUser', row.id);
} else {
this.$handler.executeAction('admin/promoteUser', row.id);
}
},
promptPurgeFiles(row) {
this.$buefy.dialog.confirm({
@ -129,10 +126,7 @@ export default {
});
},
async purgeFiles(row) {
const response = await this.$axios.$post('admin/users/purge', {
id: row.id,
});
this.$buefy.toast.open(response.message);
this.$handler.executeAction('admin/purgeUserFiles', row.id);
},
},
};

View File

@ -30,11 +30,9 @@
placeholder="Search"
type="search" />
<p class="control">
<button
outlined
class="button is-primary">
<b-button type="is-lolisafe">
Search
</button>
</b-button>
</p>
</b-field>
</div>

View File

@ -21,11 +21,9 @@
placeholder="Search"
type="search" />
<p class="control">
<button
outlined
class="button is-primary">
<b-button type="is-lolisafe">
Search
</button>
</b-button>
</p>
</b-field>
</div>

View File

@ -144,11 +144,11 @@
type="text"
@keyup.enter.native="createTag" />
<p class="control">
<button
class="button is-primary"
<b-button
type="is-lolisafe"
@click="createTag">
Create tags
</button>
</b-button>
</p>
</b-field>
</div>

View File

@ -25,20 +25,32 @@
@keyup.enter.native="login" />
</b-field>
<p class="control has-addons is-pulled-right">
<router-link
v-if="config.userAccounts"
to="/register"
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>
</p>
<p class="control has-addons is-pulled-right" />
<div class="level">
<div class="level-left">
<div class="level-item">
<router-link
v-if="config.userAccounts"
to="/register"
class="is-text">
Don't have an account?
</router-link>
<span v-else>Registration is closed at the moment</span>
</div>
</div>
<div class="level-right">
<p class="level-item">
<b-button
size="is-medium"
type="is-lolisafe"
@click="login">
Login
</b-button>
</p>
</div>
</div>
</div>
</div>
</div>
@ -99,10 +111,7 @@ export default {
const { username, password } = this;
if (!username || !password) {
this.$store.dispatch('alert/set', {
text: 'Please fill both fields before attempting to log in.',
error: true,
});
this.$notifier.error('Please fill both fields before attempting to log in.');
return;
}
@ -113,7 +122,7 @@ export default {
this.redirect();
}
} catch (e) {
this.$store.dispatch('alert/set', { text: e.message, error: true }, { root: true });
this.$notifier.error(e.message);
} finally {
this.isLoading = false;
}

View File

@ -31,19 +31,30 @@
@keyup.enter.native="register" />
</b-field>
<p class="control has-addons is-pulled-right">
<router-link
to="/login"
class="is-text">
Already have an account?
</router-link>
<button
class="button is-primary big ml1"
:disabled="isLoading"
@click="register">
Register
</button>
</p>
<div class="level">
<!-- Left side -->
<div class="level-left">
<div class="level-item">
<router-link
to="/login"
class="is-text">
Already have an account?
</router-link>
</div>
</div>
<!-- Right side -->
<div class="level-right">
<p class="level-item">
<b-button
size="is-medium"
type="is-lolisafe"
:disabled="isLoading"
@click="register">
Register
</b-button>
</p>
</div>
</div>
</div>
</div>
</div>
@ -70,32 +81,28 @@ export default {
methods: {
async register() {
if (this.isLoading) return;
if (!this.username || !this.password || !this.rePassword) {
this.$store.dispatch('alert', {
text: 'Please fill all fields before attempting to register.',
error: true,
});
this.$notifier.error('Please fill all fields before attempting to register.');
return;
}
if (this.password !== this.rePassword) {
this.$store.dispatch('alert', {
text: "Passwords don't match",
error: true,
});
this.$notifier.error('Passwords don\'t match');
return;
}
this.isLoading = true;
try {
const response = await this.$axios.$post('auth/register', {
const response = await this.$store.dispatch('auth/register', {
username: this.username,
password: this.password,
});
this.$store.dispatch('alert', { text: response.message });
return this.$router.push('/login');
this.$notifier.success(response.message);
this.$router.push('/login');
return;
} catch (error) {
//
this.$notifier.error(error.message);
} finally {
this.isLoading = false;
}

View File

@ -0,0 +1,93 @@
export const state = () => ({
users: [],
user: {
id: null,
username: null,
enabled: false,
createdAt: null,
editedAt: null,
apiKeyEditedAt: null,
isAdmin: null,
files: [],
},
file: {},
settings: {},
});
export const actions = {
async fetchUsers({ commit }) {
const response = await this.$axios.$get('admin/users');
commit('setUsers', response);
return response;
},
async fetchUser({ commit }, id) {
const response = await this.$axios.$get(`/admin/users/${id}`);
commit('setUserInfo', response);
return response;
},
async enableUser({ commit }, id) {
const response = await this.$axios.$post('admin/users/enable', { id });
commit('changeUserState', { userId: id, enabled: true });
return response;
},
async disableUser({ commit }, id) {
const response = await this.$axios.$post('admin/users/disable', { id });
commit('changeUserState', { userId: id, enabled: false });
return response;
},
async promoteUser({ commit }, id) {
const response = await this.$axios.$post('admin/users/promote', { id });
commit('changeUserState', { userId: id, isAdmin: true });
return response;
},
async demoteUser({ commit }, id) {
const response = await this.$axios.$post('admin/users/demote', { id });
commit('changeUserState', { userId: id, isAdmin: false });
return response;
},
async purgeUserFiles(_, id) {
const response = await this.$axios.$post('admin/users/purge', { id });
return response;
},
};
export const mutations = {
setUsers(state, { users }) {
state.users = users;
},
setUserInfo(state, { user, files }) {
state.user = { ...state.user, ...user };
state.user.files = files || [];
},
changeUserState(state, { userId, enabled, isAdmin }) {
const foundIndex = state.users.findIndex(({ id }) => id === userId);
if (foundIndex > -1) {
if (enabled !== undefined) {
state.users[foundIndex].enabled = enabled;
}
if (isAdmin !== undefined) {
state.users[foundIndex].isAdmin = isAdmin;
}
}
if (state.user.id === userId) {
if (enabled !== undefined) {
state.user.enabled = enabled;
}
if (isAdmin !== undefined) {
state.user.isAdmin = isAdmin;
}
}
},
};

View File

@ -1,12 +1,19 @@
import AlertTypes from '~/constants/alertTypes';
const getDefaultState = () => ({
text: null,
error: false,
message: null,
type: null,
snackbar: false,
});
export const state = getDefaultState;
export const actions = {
set({ commit }, data) {
// Only exists for backwards compatibility, remove one day
if (data.error === true) data.type = AlertTypes.ERROR;
if (data.text !== undefined) data.message = data.text;
commit('set', data);
},
clear({ commit }) {
@ -15,9 +22,10 @@ export const actions = {
};
export const mutations = {
set(state, { text, error }) {
state.text = text;
state.error = error;
set(state, { message, type, snackbar }) {
state.message = message;
state.type = type;
state.snackbar = snackbar || false;
},
clear(state) {
Object.assign(state, getDefaultState());

View File

@ -30,6 +30,12 @@ export const actions = {
commit('setToken', data.token);
commit('loginSuccess', { token: data.token, user: data.user });
},
async register(_, { username, password }) {
return this.$axios.$post('auth/register', {
username,
password,
});
},
async fetchCurrentUser({ commit, dispatch }) {
try {
const data = await this.$axios.$get('users/me');
@ -83,13 +89,13 @@ export const mutations = {
state.isLoading = true;
},
loginSuccess(state, { user }) {
this.$cookies.set('token', state.token);
this.$cookies.set('token', state.token, { path: '/' });
state.user = user;
state.loggedIn = true;
state.isLoading = false;
},
logout(state) {
this.$cookies.remove('token');
this.$cookies.remove('token', { path: '/' });
// reset state to default
Object.assign(state, getDefaultState());
},

View File

@ -10,7 +10,7 @@ export const getDefaultState = () => ({
},
name: null,
downloadEnabled: false,
filesAlbums: {},
filesAlbums: {}, // map of file ids with a list of album objects the file is in
});
export const state = getDefaultState;