mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-07-06 03:20:55 +00:00
added admin tools
This commit is contained in:
parent
c43d9833f5
commit
b19b114d90
14 changed files with 621 additions and 66 deletions
|
|
@ -53,6 +53,7 @@
|
||||||
import_contacts
|
import_contacts
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
data-name="Click Me"
|
||||||
v-if="!user.survey_completed"
|
v-if="!user.survey_completed"
|
||||||
class="item material-icons"
|
class="item material-icons"
|
||||||
@click="openSurvey"
|
@click="openSurvey"
|
||||||
|
|
@ -60,6 +61,15 @@
|
||||||
>
|
>
|
||||||
error
|
error
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isAdmin"
|
||||||
|
class="item material-icons"
|
||||||
|
:class="{ selected: currentTab == 4 }"
|
||||||
|
@click="switchTab(4)"
|
||||||
|
@mouseenter="localToolTipEvent('Admin Panel', $event)"
|
||||||
|
>
|
||||||
|
security
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -128,37 +138,8 @@ export default {
|
||||||
this.$store.dispatch("servers/setSelectedServerID", serverID);
|
this.$store.dispatch("servers/setSelectedServerID", serverID);
|
||||||
this.$store.dispatch("openChannel", channel);
|
this.$store.dispatch("openChannel", channel);
|
||||||
},
|
},
|
||||||
switchChannel(isServer) {
|
|
||||||
const serverChannelID = this.$store.state.channelModule.serverChannelID;
|
|
||||||
const DMChannelID = this.$store.state.channelModule.DMChannelID;
|
|
||||||
|
|
||||||
if (isServer) {
|
|
||||||
this.$store.dispatch("selectedChannelID", serverChannelID);
|
|
||||||
const channel = this.$store.state.channelModule.channels[
|
|
||||||
serverChannelID
|
|
||||||
];
|
|
||||||
this.$store.dispatch("setChannelName", channel ? channel.name : "");
|
|
||||||
this.dismissNotification(serverChannelID);
|
|
||||||
} else {
|
|
||||||
const channel = this.$store.state.channelModule.channels[DMChannelID];
|
|
||||||
this.$store.dispatch(
|
|
||||||
"setChannelName",
|
|
||||||
channel ? channel.recipients[0].username : ""
|
|
||||||
);
|
|
||||||
this.$store.dispatch("selectedChannelID", DMChannelID);
|
|
||||||
this.dismissNotification(DMChannelID);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
switchTab(index) {
|
switchTab(index) {
|
||||||
localStorage.setItem("currentTab", index);
|
bus.$emit('tab:switch', index)
|
||||||
this.$store.dispatch("setCurrentTab", index);
|
|
||||||
if (index == 1) {
|
|
||||||
//1: direct message tab.
|
|
||||||
this.switchChannel(false);
|
|
||||||
} else if (index === 2) {
|
|
||||||
//2: server tab
|
|
||||||
this.switchChannel(true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
openSettings() {
|
openSettings() {
|
||||||
this.$store.dispatch("setPopoutVisibility", {
|
this.$store.dispatch("setPopoutVisibility", {
|
||||||
|
|
@ -177,7 +158,6 @@ export default {
|
||||||
this.toolTipLeftPosition = rect.left - tooltipWidth / 2 + 25;
|
this.toolTipLeftPosition = rect.left - tooltipWidth / 2 + 25;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
mouseLeaveEvent() {
|
mouseLeaveEvent() {
|
||||||
this.toolTipShown = false;
|
this.toolTipShown = false;
|
||||||
this.toolTipServerID = null;
|
this.toolTipServerID = null;
|
||||||
|
|
@ -206,32 +186,12 @@ export default {
|
||||||
user() {
|
user() {
|
||||||
return this.$store.getters.user;
|
return this.$store.getters.user;
|
||||||
},
|
},
|
||||||
|
isAdmin() {
|
||||||
|
return this.user.admin === 3 || this.user.admin === 4;
|
||||||
|
},
|
||||||
currentTab() {
|
currentTab() {
|
||||||
return this.$store.getters.currentTab;
|
return this.$store.getters.currentTab;
|
||||||
},
|
},
|
||||||
servers() {
|
|
||||||
return this.$store.getters["servers/servers"];
|
|
||||||
},
|
|
||||||
serversArr: {
|
|
||||||
get() {
|
|
||||||
const data = this.servers;
|
|
||||||
return Object.keys(data)
|
|
||||||
.map(key => {
|
|
||||||
return data[key];
|
|
||||||
})
|
|
||||||
.reverse();
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
const reversedServers = value.reverse();
|
|
||||||
// convert array to json
|
|
||||||
const json = {};
|
|
||||||
for (let index = 0; index < reversedServers.length; index++) {
|
|
||||||
const element = reversedServers[index];
|
|
||||||
json[element.server_id] = element;
|
|
||||||
}
|
|
||||||
this.$store.dispatch("servers/setServers", json);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedServerID() {
|
selectedServerID() {
|
||||||
return this.$store.getters["servers/selectedServerID"];
|
return this.$store.getters["servers/selectedServerID"];
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
switchTab(index) {
|
switchTab(index) {
|
||||||
localStorage.setItem("currentTab", index);
|
bus.$emit('tab:switch', index)
|
||||||
this.$store.dispatch("setCurrentTab", index);
|
|
||||||
if (index == 1) {
|
|
||||||
//1: direct message tab.
|
|
||||||
this.switchChannel(false);
|
|
||||||
} else if (index === 2) {
|
|
||||||
//2: server tab
|
|
||||||
this.switchChannel(true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
openSettings() {
|
openSettings() {
|
||||||
this.$store.dispatch("setPopoutVisibility", {
|
this.$store.dispatch("setPopoutVisibility", {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/>
|
<server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/>
|
||||||
<server-context key="sc" v-if="popouts.allPopout.type === 'SERVER' && popouts.allPopout.show"/>
|
<server-context key="sc" v-if="popouts.allPopout.type === 'SERVER' && popouts.allPopout.show"/>
|
||||||
<add-friend key="af" v-if="popouts.allPopout.type === 'ADD_FRIEND' && popouts.allPopout.show"/>
|
<add-friend key="af" v-if="popouts.allPopout.type === 'ADD_FRIEND' && popouts.allPopout.show"/>
|
||||||
|
<admin-css-editor key="ace" v-if="popouts.allPopout.type === 'ADMIN_CSS_EDITOR'" />
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
const ServerInvitePopout = () => import('./Popouts/ServerInvitePopout.vue');
|
const ServerInvitePopout = () => import('./Popouts/ServerInvitePopout.vue');
|
||||||
const ServerSettings = () => import('./Popouts/ServerSettingsPanels/ServerSettings.vue');
|
const ServerSettings = () => import('./Popouts/ServerSettingsPanels/ServerSettings.vue');
|
||||||
const GenericPopout = () => import('./Popouts/GenericPopout');
|
const GenericPopout = () => import('./Popouts/GenericPopout');
|
||||||
|
const AdminCssEditor = () => import('./Popouts/AdminEditorPopout');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,7 +63,8 @@ export default {
|
||||||
messageContextMenu,
|
messageContextMenu,
|
||||||
ServerMemberContext,
|
ServerMemberContext,
|
||||||
ServerContext,
|
ServerContext,
|
||||||
AddFriend
|
AddFriend,
|
||||||
|
AdminCssEditor
|
||||||
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
|
||||||
183
src/components/app/Popouts/Popouts/AdminEditorPopout.vue
Normal file
183
src/components/app/Popouts/Popouts/AdminEditorPopout.vue
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
<template>
|
||||||
|
<div class="dark-background" @mousedown="backgroundClick">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="details" v-if="details && details.updatedCss">
|
||||||
|
<div class="item">Updated</div>
|
||||||
|
<div class="item">Current</div>
|
||||||
|
</div>
|
||||||
|
<codemirror
|
||||||
|
v-if="details"
|
||||||
|
class="editor"
|
||||||
|
:merge="!!details.updatedCss"
|
||||||
|
:code="details.css"
|
||||||
|
:options="cmOptions"
|
||||||
|
/>
|
||||||
|
<div class="button" @click="applyButton">Apply</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import "codemirror/mode/css/css.js";
|
||||||
|
|
||||||
|
import 'codemirror/addon/merge/merge.js'
|
||||||
|
// merge css
|
||||||
|
import 'codemirror/addon/merge/merge.css'
|
||||||
|
// google DiffMatchPatch
|
||||||
|
import DiffMatchPatch from 'diff-match-patch'
|
||||||
|
|
||||||
|
|
||||||
|
window.diff_match_patch = DiffMatchPatch
|
||||||
|
window.DIFF_DELETE = -1
|
||||||
|
window.DIFF_INSERT = 1
|
||||||
|
window.DIFF_EQUAL = 0
|
||||||
|
|
||||||
|
// theme css
|
||||||
|
import "codemirror/theme/base16-dark.css";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import { codemirror } from "vue-codemirror";
|
||||||
|
|
||||||
|
import config from "@/config.js";
|
||||||
|
import { bus } from "@/main";
|
||||||
|
import Spinner from "@/components/Spinner.vue";
|
||||||
|
import AdminService from "@/services/adminService";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { codemirror },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
details: null,
|
||||||
|
code: null,
|
||||||
|
cmOptions: {
|
||||||
|
value: ``,
|
||||||
|
origLeft: null,
|
||||||
|
orig: ``,
|
||||||
|
// codemirror options
|
||||||
|
tabSize: 2,
|
||||||
|
mode: "text/css",
|
||||||
|
theme: "base16-dark",
|
||||||
|
lineNumbers: true,
|
||||||
|
line: true,
|
||||||
|
readOnly: true,
|
||||||
|
collapseIdentical: false,
|
||||||
|
highlightDifferences: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeMenu() {
|
||||||
|
this.$store.dispatch("setAllPopout", {
|
||||||
|
show: false,
|
||||||
|
type: null,
|
||||||
|
id: null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
backgroundClick(e) {
|
||||||
|
if (e.target.classList.contains("dark-background")) {
|
||||||
|
this.closeMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyButton() {
|
||||||
|
const css = this.details.updatedCss || this.details.css;
|
||||||
|
const styleEl = document.createElement("style");
|
||||||
|
styleEl.id = "theme";
|
||||||
|
styleEl.innerHTML = css;
|
||||||
|
|
||||||
|
const currentStyle = document.getElementById("theme");
|
||||||
|
if (currentStyle) {
|
||||||
|
currentStyle.outerHTML = styleEl.outerHTML;
|
||||||
|
} else {
|
||||||
|
document.head.innerHTML += styleEl.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const { ok, result, error } = await AdminService.fetchTheme(
|
||||||
|
this.popoutDetails.id
|
||||||
|
);
|
||||||
|
if (ok) {
|
||||||
|
this.cmOptions.orig = result.data.css;
|
||||||
|
this.cmOptions.value = result.data.updatedCss;
|
||||||
|
this.details = result.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
popoutDetails() {
|
||||||
|
return this.$store.getters.popouts.allPopout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dark-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.541);
|
||||||
|
z-index: 111111;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
margin: auto;
|
||||||
|
max-height: 460px;
|
||||||
|
height: 100%;
|
||||||
|
width: 800px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 0px 20px 5px #151515bd;
|
||||||
|
background: linear-gradient(#0b4155, #01677e);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
padding: 10px;
|
||||||
|
transition: 0.3s;
|
||||||
|
display: inline;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
.item {
|
||||||
|
flex: 1;
|
||||||
|
padding: 2px;
|
||||||
|
&:nth-child(2) {
|
||||||
|
margin-left: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.CodeMirror {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-merge-pane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.CodeMirror-merge-r-chunk {
|
||||||
|
background: rgb(36, 36, 36);
|
||||||
|
}
|
||||||
|
.CodeMirror-merge-r-chunk-end {
|
||||||
|
border-bottom: 1px solid rgb(66, 66, 66);
|
||||||
|
}
|
||||||
|
.CodeMirror-merge-r-chunk-start {
|
||||||
|
border-top: 1px solid rgb(66, 66, 66);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
/>
|
/>
|
||||||
<div class="managing" v-else>
|
<div class="managing" v-else>
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
<div class="button">
|
<div class="button" @click="exploreButton">
|
||||||
<div class="material-icons">explore</div>
|
<div class="material-icons">explore</div>
|
||||||
Explore
|
Explore
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {bus} from '@/main'
|
||||||
import ThemeTemplate from "./MyThemeTemplate";
|
import ThemeTemplate from "./MyThemeTemplate";
|
||||||
import Editor from "./themesEditor";
|
import Editor from "./themesEditor";
|
||||||
import MakePublic from "./MyThemesMakePublic";
|
import MakePublic from "./MyThemesMakePublic";
|
||||||
|
|
@ -155,6 +156,13 @@ export default {
|
||||||
},
|
},
|
||||||
makePublicButton() {
|
makePublicButton() {
|
||||||
this.showMakePublic = true;
|
this.showMakePublic = true;
|
||||||
|
},
|
||||||
|
exploreButton() {
|
||||||
|
this.$store.dispatch("setPopoutVisibility", {
|
||||||
|
name: "settings",
|
||||||
|
visibility: false
|
||||||
|
});
|
||||||
|
bus.$emit('tab:switch', 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|
|
||||||
48
src/components/app/Tabs/AdminPanel.vue
Normal file
48
src/components/app/Tabs/AdminPanel.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div class="admin-panel-tab">
|
||||||
|
<users-panel />
|
||||||
|
<online-users-panel />
|
||||||
|
<themes-panel />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { bus } from "@/main";
|
||||||
|
|
||||||
|
import UsersPanel from './AdminPanel/UsersPanel';
|
||||||
|
import OnlineUsersPanel from './AdminPanel/OnlineUsersPanel';
|
||||||
|
import ThemesPanel from './AdminPanel/ThemesPanel';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { UsersPanel, OnlineUsersPanel, ThemesPanel },
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.isAdmin) {
|
||||||
|
bus.$emit('tab:switch', 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user() {
|
||||||
|
return this.$store.getters.user;
|
||||||
|
},
|
||||||
|
isAdmin() {
|
||||||
|
return this.user.admin === 3 || this.user.admin === 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.admin-panel-tab {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/components/app/Tabs/AdminPanel/OnlineUsersPanel.vue
Normal file
49
src/components/app/Tabs/AdminPanel/OnlineUsersPanel.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div class="users-panel">
|
||||||
|
<div class="title">Online Users</div>
|
||||||
|
<div class="list">
|
||||||
|
<user-template v-for="user in users" :key="user.uniqueID" :user="user" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import UserTemplate from './UserTemplate';
|
||||||
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {UserTemplate},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
users: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const {ok, error, result} = await adminService.fetchOnlineUsers();
|
||||||
|
if (ok) {
|
||||||
|
this.users = result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.users-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
height: 100%;
|
||||||
|
width: 300px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
110
src/components/app/Tabs/AdminPanel/ThemeTemplate.vue
Normal file
110
src/components/app/Tabs/AdminPanel/ThemeTemplate.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<div class="theme" v-if="!approved">
|
||||||
|
<div class="screenshot" @click="screenshotClicked" :style="{backgroundImage: `url(${screenshot})`}"></div>
|
||||||
|
<div class="details">
|
||||||
|
<div class="name"><strong>Name:</strong> {{theme.theme.name}}</div>
|
||||||
|
<strong>Description:</strong>
|
||||||
|
<div class="description">{{theme.description}}</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<div class="button" v-if="theme.approved" @click="openEditor">Compare</div>
|
||||||
|
<div class="button" v-else @click="openEditor">View code</div>
|
||||||
|
<div class="button" v-if="theme.approved" @click="approveButton">Update</div>
|
||||||
|
<div class="button" v-else @click="approveButton">Approve</div>
|
||||||
|
<div class="button">Decline</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import config from "@/config.js";
|
||||||
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['theme'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
approved: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
screenshot() {
|
||||||
|
return config.domain + "/media/" + this.theme.screenshot;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
screenshotClicked() {
|
||||||
|
this.$store.dispatch(
|
||||||
|
"setImagePreviewURL",
|
||||||
|
this.screenshot
|
||||||
|
);
|
||||||
|
},
|
||||||
|
openEditor() {
|
||||||
|
this.$store.dispatch("setAllPopout", {
|
||||||
|
show: true,
|
||||||
|
type: "ADMIN_CSS_EDITOR",
|
||||||
|
id: this.theme.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async approveButton() {
|
||||||
|
const {ok, result, error} = await adminService.approveTheme(this.theme.id);
|
||||||
|
if (ok) {
|
||||||
|
this.approved = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.theme {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.screenshot {
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
max-height: 100px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
.button {
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: auto;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: 0.2s;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/components/app/Tabs/AdminPanel/ThemesPanel.vue
Normal file
49
src/components/app/Tabs/AdminPanel/ThemesPanel.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="title">Unapproved Themes</div>
|
||||||
|
<div class="list">
|
||||||
|
<theme-template v-for="theme in themes" :key="theme.id" :theme="theme" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ThemeTemplate from './ThemeTemplate';
|
||||||
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {ThemeTemplate},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
themes: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const {ok, error, result} = await adminService.fetchWaitingThemes();
|
||||||
|
if (ok) {
|
||||||
|
this.themes = result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
height: 100%;
|
||||||
|
width: 300px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
73
src/components/app/Tabs/AdminPanel/UserTemplate.vue
Normal file
73
src/components/app/Tabs/AdminPanel/UserTemplate.vue
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div class="user" @click="openUserInformation">
|
||||||
|
<div class="profile-picture" :style="{backgroundImage: `url(${avatar})`}"></div>
|
||||||
|
<div class="details">
|
||||||
|
<div class="username-tag">{{user.username}}<span class="tag">@{{user.tag}}</span></div>
|
||||||
|
<div class="date" v-if="!presence">{{date}}</div>
|
||||||
|
<div class="presence" v-if="presence">{{presence}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import friendlyDate from '@/utils/date';
|
||||||
|
import config from "@/config.js";
|
||||||
|
import statuses from '@/utils/statuses';
|
||||||
|
export default {
|
||||||
|
props: ['user'],
|
||||||
|
computed: {
|
||||||
|
date() {
|
||||||
|
return friendlyDate(this.user.created)
|
||||||
|
},
|
||||||
|
avatar() {
|
||||||
|
return config.domain + "/avatars/" + this.user.avatar;
|
||||||
|
},
|
||||||
|
presence() {
|
||||||
|
if (this.user.status === undefined) return null;
|
||||||
|
return statuses[this.user.status].name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openUserInformation() {
|
||||||
|
this.$store.dispatch("setUserInformationPopout", this.user.uniqueID);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.user {
|
||||||
|
display: flex;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.profile-picture {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 5px;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.presence {
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/components/app/Tabs/AdminPanel/UsersPanel.vue
Normal file
49
src/components/app/Tabs/AdminPanel/UsersPanel.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div class="users-panel">
|
||||||
|
<div class="title">Recently Created Accounts</div>
|
||||||
|
<div class="list">
|
||||||
|
<user-template v-for="user in users" :key="user.uniqueID" :user="user" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import UserTemplate from './UserTemplate';
|
||||||
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {UserTemplate},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
users: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const {ok, error, result} = await adminService.fetchRecentCreatedUsers();
|
||||||
|
if (ok) {
|
||||||
|
this.users = result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.users-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
height: 100%;
|
||||||
|
width: 300px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/services/adminService.js
Normal file
20
src/services/adminService.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {instance, wrapper} from './Api';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetchRecentCreatedUsers () {
|
||||||
|
return wrapper(instance().get(`admin/users/recent`))
|
||||||
|
},
|
||||||
|
fetchOnlineUsers() {
|
||||||
|
return wrapper(instance().get(`admin/users/online`))
|
||||||
|
},
|
||||||
|
fetchWaitingThemes() {
|
||||||
|
return wrapper(instance().get(`admin/themes/waiting`))
|
||||||
|
},
|
||||||
|
fetchTheme(id) {
|
||||||
|
return wrapper(instance().get(`admin/themes/${id}`));
|
||||||
|
},
|
||||||
|
approveTheme(id) {
|
||||||
|
return wrapper(instance().patch(`admin/themes/${id}/approve`));
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ const state = {
|
||||||
serverID: null,
|
serverID: null,
|
||||||
uniqueID: null,
|
uniqueID: null,
|
||||||
creatorUniqueID: null,
|
creatorUniqueID: null,
|
||||||
|
id: null,
|
||||||
x: null,
|
x: null,
|
||||||
y: null
|
y: null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<direct-message v-if="currentTab == 1" />
|
<direct-message v-if="currentTab == 1" />
|
||||||
<servers v-if="currentTab == 2" />
|
<servers v-if="currentTab == 2" />
|
||||||
<explore v-if="currentTab == 0" />
|
<explore v-if="currentTab == 0" />
|
||||||
|
<admin-panel v-if="currentTab == 4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
@ -57,6 +58,11 @@ const Explore = () => ({
|
||||||
loading: Spinner,
|
loading: Spinner,
|
||||||
delay: 0
|
delay: 0
|
||||||
});
|
});
|
||||||
|
const AdminPanel = () => ({
|
||||||
|
component: import("./../components/app/Tabs/AdminPanel.vue"),
|
||||||
|
loading: Spinner,
|
||||||
|
delay: 0
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "app",
|
name: "app",
|
||||||
|
|
@ -66,6 +72,7 @@ export default {
|
||||||
ConnectingScreen,
|
ConnectingScreen,
|
||||||
Popouts,
|
Popouts,
|
||||||
News,
|
News,
|
||||||
|
AdminPanel,
|
||||||
ElectronFrameButtons,
|
ElectronFrameButtons,
|
||||||
Explore,
|
Explore,
|
||||||
MainNav
|
MainNav
|
||||||
|
|
@ -140,7 +147,7 @@ export default {
|
||||||
css = exploreThemes.result.data.css;
|
css = exploreThemes.result.data.css;
|
||||||
id = exploreThemes.result.data.id;
|
id = exploreThemes.result.data.id;
|
||||||
}
|
}
|
||||||
if (privateThemes) {
|
if (privateThemes.ok) {
|
||||||
css = privateThemes.result.data.css;
|
css = privateThemes.result.data.css;
|
||||||
id = privateThemes.result.data.id;
|
id = privateThemes.result.data.id;
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +179,9 @@ export default {
|
||||||
bus.$on("title:change", title => {
|
bus.$on("title:change", title => {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
});
|
});
|
||||||
|
bus.$on("tab:switch", tab => {
|
||||||
|
this.switchTab(tab);
|
||||||
|
});
|
||||||
this.setTheme();
|
this.setTheme();
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue