added admin tools

This commit is contained in:
unknown 2019-12-11 14:10:00 +00:00
parent c43d9833f5
commit b19b114d90
14 changed files with 621 additions and 66 deletions

View file

@ -53,6 +53,7 @@
import_contacts
</div>
<div
data-name="Click Me"
v-if="!user.survey_completed"
class="item material-icons"
@click="openSurvey"
@ -60,6 +61,15 @@
>
error
</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
@ -128,37 +138,8 @@ export default {
this.$store.dispatch("servers/setSelectedServerID", serverID);
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) {
localStorage.setItem("currentTab", 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);
}
bus.$emit('tab:switch', index)
},
openSettings() {
this.$store.dispatch("setPopoutVisibility", {
@ -177,7 +158,6 @@ export default {
this.toolTipLeftPosition = rect.left - tooltipWidth / 2 + 25;
});
},
mouseLeaveEvent() {
this.toolTipShown = false;
this.toolTipServerID = null;
@ -206,32 +186,12 @@ export default {
user() {
return this.$store.getters.user;
},
isAdmin() {
return this.user.admin === 3 || this.user.admin === 4;
},
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() {
return this.$store.getters["servers/selectedServerID"];
},

View file

@ -121,15 +121,7 @@ export default {
}
},
switchTab(index) {
localStorage.setItem("currentTab", 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);
}
bus.$emit('tab:switch', index)
},
openSettings() {
this.$store.dispatch("setPopoutVisibility", {

View file

@ -16,6 +16,7 @@
<server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/>
<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"/>
<admin-css-editor key="ace" v-if="popouts.allPopout.type === 'ADMIN_CSS_EDITOR'" />
</transition-group>
</div>
</template>
@ -42,6 +43,7 @@
const ServerInvitePopout = () => import('./Popouts/ServerInvitePopout.vue');
const ServerSettings = () => import('./Popouts/ServerSettingsPanels/ServerSettings.vue');
const GenericPopout = () => import('./Popouts/GenericPopout');
const AdminCssEditor = () => import('./Popouts/AdminEditorPopout');
@ -61,7 +63,8 @@ export default {
messageContextMenu,
ServerMemberContext,
ServerContext,
AddFriend
AddFriend,
AdminCssEditor
},
data() {

View 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>

View file

@ -11,7 +11,7 @@
/>
<div class="managing" v-else>
<div class="bar">
<div class="button">
<div class="button" @click="exploreButton">
<div class="material-icons">explore</div>
Explore
</div>
@ -39,6 +39,7 @@
</template>
<script>
import {bus} from '@/main'
import ThemeTemplate from "./MyThemeTemplate";
import Editor from "./themesEditor";
import MakePublic from "./MyThemesMakePublic";
@ -155,6 +156,13 @@ export default {
},
makePublicButton() {
this.showMakePublic = true;
},
exploreButton() {
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: false
});
bus.$emit('tab:switch', 0)
}
},
async mounted() {

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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`));
},
}

View file

@ -49,6 +49,7 @@ const state = {
serverID: null,
uniqueID: null,
creatorUniqueID: null,
id: null,
x: null,
y: null
}

View file

@ -16,6 +16,7 @@
<direct-message v-if="currentTab == 1" />
<servers v-if="currentTab == 2" />
<explore v-if="currentTab == 0" />
<admin-panel v-if="currentTab == 4" />
</div>
</div>
</transition>
@ -57,6 +58,11 @@ const Explore = () => ({
loading: Spinner,
delay: 0
});
const AdminPanel = () => ({
component: import("./../components/app/Tabs/AdminPanel.vue"),
loading: Spinner,
delay: 0
});
export default {
name: "app",
@ -66,6 +72,7 @@ export default {
ConnectingScreen,
Popouts,
News,
AdminPanel,
ElectronFrameButtons,
Explore,
MainNav
@ -140,7 +147,7 @@ export default {
css = exploreThemes.result.data.css;
id = exploreThemes.result.data.id;
}
if (privateThemes) {
if (privateThemes.ok) {
css = privateThemes.result.data.css;
id = privateThemes.result.data.id;
}
@ -172,6 +179,9 @@ export default {
bus.$on("title:change", title => {
this.title = title;
});
bus.$on("tab:switch", tab => {
this.switchTab(tab);
});
this.setTheme();
},