Compare commits
3 Commits
8ea5c46920
...
f5388abeb0
Author | SHA1 | Date | |
---|---|---|---|
f5388abeb0 | |||
6a92858ac1 | |||
7292700cc9 |
@ -14,6 +14,7 @@
|
|||||||
"jdenticon": "^3.1.0",
|
"jdenticon": "^3.1.0",
|
||||||
"matrix-js-sdk": "^9.1.0",
|
"matrix-js-sdk": "^9.1.0",
|
||||||
"sass": "^1.29.0",
|
"sass": "^1.29.0",
|
||||||
|
"superagent": "^6.1.0",
|
||||||
"v-emoji-picker": "^2.3.1",
|
"v-emoji-picker": "^2.3.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
|
4
public/sym/ic_add_white.svg
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 210 B |
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
1
public/sym/ic_create_white.svg
Executable file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="24px" height="24px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM5.92 19H5v-.92l9.06-9.06.92.92L5.92 19zM20.71 5.63l-2.34-2.34c-.2-.2-.45-.29-.71-.29s-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41z"/></svg>
|
After Width: | Height: | Size: 357 B |
4
public/sym/ic_delete_white.svg
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 465 B |
19
src/App.vue
@ -22,6 +22,23 @@ export default {
|
|||||||
body{
|
body{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
input{
|
||||||
|
padding: 0 2rem 0 2rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1d1d1d;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
appearance: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input:focus{
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
*{
|
*{
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #42b983 #2220;
|
scrollbar-color: #42b983 #2220;
|
||||||
@ -50,7 +67,7 @@ body{
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: calc(50% - 35rem);
|
left: calc(50% - 35rem);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: calc(100%);
|
min-height: 100%;
|
||||||
width: 70rem;
|
width: 70rem;
|
||||||
min-width: 18rem;
|
min-width: 18rem;
|
||||||
background-color: #313131;
|
background-color: #313131;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="iconContainer" >
|
<button class="iconContainer" >
|
||||||
<img class="icon" v-bind:src=ic alt="" />
|
<img class="icon" :src=ic alt="" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ name: "icon",
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.iconContainer{
|
.iconContainer{
|
||||||
|
position: relative;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
<form class="newMessageBanner" ref="newMessageBanner" v-on:submit.prevent="sendMessage()">
|
<form class="newMessageBanner" ref="newMessageBanner" v-on:submit.prevent="sendMessage()">
|
||||||
<label for="newMessageInput"></label>
|
<label for="newMessageInput"></label>
|
||||||
<textarea
|
<textarea
|
||||||
@keyup.enter.exact="sendMessage()"
|
@keyup.enter.exact="sendMessage()"
|
||||||
@input="resizeMessageBanner()"
|
@input="resizeMessageBanner()"
|
||||||
v-model="msg.content.body"
|
v-model="msg.content.body"
|
||||||
ref="newMessageInput"
|
ref="newMessageInput"
|
||||||
id="newMessageInput"
|
id="newMessageInput"
|
||||||
class="newMessageInput"
|
class="newMessageInput"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
rows="1"
|
rows="1"
|
||||||
placeholder="type a message ..."
|
placeholder="type a message ..."
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
type="submit"
|
type="submit"
|
||||||
title="press enter to submit"
|
title="press enter to submit"
|
||||||
class="sendMessageBtn"
|
class="sendMessageBtn"
|
||||||
ic="./sym/ic_send_white_24px.svg"
|
ic="./sym/ic_send_white.svg"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,7 +25,9 @@ export default {
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
.overlay{
|
.overlay{
|
||||||
position: absolute;
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #111d;
|
background-color: #111d;
|
||||||
|
45
src/lib/AdminAPI.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import rest from 'superagent';
|
||||||
|
|
||||||
|
export class AdminAPI{
|
||||||
|
constructor(baseUrl, accessToken){
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendRequest({path, obj={}, method=rest=>rest.get}){
|
||||||
|
return await method(rest)(`${this.baseUrl}${path}?access_token=${this.accessToken}`)
|
||||||
|
.send(obj).set('accept', 'json').then(res => {
|
||||||
|
return JSON.parse(res.text);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers(){
|
||||||
|
return await this.sendRequest({path: 'v2/users'});
|
||||||
|
}
|
||||||
|
async resetPassword({userId, password}){
|
||||||
|
return await this.sendRequest({
|
||||||
|
path: `v1/reset_password/${userId}`,
|
||||||
|
method: rest=>rest.post,
|
||||||
|
obj: {
|
||||||
|
new_password: password,
|
||||||
|
logout_devices: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async updateUser({userId, password, displayname, avatar_url=undefined}){
|
||||||
|
return await this.sendRequest({
|
||||||
|
path: `v2/users/${userId}`,
|
||||||
|
method: rest=>rest.put,
|
||||||
|
obj: {
|
||||||
|
password,
|
||||||
|
displayname,
|
||||||
|
avatar_url,
|
||||||
|
admin: false,
|
||||||
|
deactivated: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ export class MatrixHandler {
|
|||||||
callback(response.access_token);
|
callback(response.access_token);
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
|
this.accessToken = response.access_token;
|
||||||
this.startSync()
|
this.startSync()
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -49,6 +50,7 @@ export class MatrixHandler {
|
|||||||
});
|
});
|
||||||
this.user = userId;
|
this.user = userId;
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
|
this.accessToken = accessToken;
|
||||||
this.startSync();
|
this.startSync();
|
||||||
}
|
}
|
||||||
async logout(){
|
async logout(){
|
||||||
|
@ -2,6 +2,7 @@ import VueRouter from 'vue-router';
|
|||||||
import login from '@/views/login';
|
import login from '@/views/login';
|
||||||
import chat from '@/views/chat';
|
import chat from '@/views/chat';
|
||||||
import rooms from '@/views/rooms';
|
import rooms from '@/views/rooms';
|
||||||
|
import admin from '@/views/admin';
|
||||||
|
|
||||||
export const router = new VueRouter({
|
export const router = new VueRouter({
|
||||||
routes: [
|
routes: [
|
||||||
@ -29,6 +30,11 @@ export const router = new VueRouter({
|
|||||||
path: '/rooms',
|
path: '/rooms',
|
||||||
name: 'rooms',
|
name: 'rooms',
|
||||||
component: rooms
|
component: rooms
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
name: 'admin',
|
||||||
|
component: admin
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
153
src/views/admin.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="accessDenied">access denied!</div>
|
||||||
|
<div class="admin" v-else-if="users !== undefined">
|
||||||
|
<h1>admin</h1>
|
||||||
|
<h2>users</h2>
|
||||||
|
<div class="tableScroll">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th class="left">user</th>
|
||||||
|
<th class="mid">displayname</th>
|
||||||
|
<th class="mid">deactivated</th>
|
||||||
|
<th class="mid">admin</th>
|
||||||
|
<th class="right edit">edit</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="user in users.users" :key="user.name">
|
||||||
|
<td class="left">{{user.name}}</td>
|
||||||
|
<td class="mid">{{user.displayname}}</td>
|
||||||
|
<td class="mid">{{user.deactivated}}</td>
|
||||||
|
<td class="mid">{{user.admin}}</td>
|
||||||
|
<td class="right edit">
|
||||||
|
<icon @click.native="newUser.userId = user.name" class="editIcon" type="submit" ic="./sym/ic_create_white.svg"/>
|
||||||
|
<icon class="editIcon" type="submit" ic="./sym/ic_delete_white.svg"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h2>update user</h2>
|
||||||
|
<div class="update">
|
||||||
|
<input v-model="newUser.userId" type="text" placeholder="userId"><br>
|
||||||
|
<input v-model="newUser.password" type="password" placeholder="password"><br>
|
||||||
|
<input v-model="newUser.displayname" type="text" placeholder="displayname">
|
||||||
|
</div>
|
||||||
|
<textbtn @click.native="updateUser()" text="update user"/>
|
||||||
|
</div>
|
||||||
|
<throbber-overlay v-if="loading" :text="loading"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {matrix} from "@/main";
|
||||||
|
import {AdminAPI} from "@/lib/AdminAPI";
|
||||||
|
import icon from "@/components/icon";
|
||||||
|
import textbtn from "@/components/textbtn";
|
||||||
|
import ThrobberOverlay from "@/components/throbberOverlay";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "admin",
|
||||||
|
components:{
|
||||||
|
ThrobberOverlay,
|
||||||
|
icon,
|
||||||
|
textbtn
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
users: undefined,
|
||||||
|
newUser:{
|
||||||
|
userId: '',
|
||||||
|
password: '',
|
||||||
|
displayname: '',
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
accessDenied: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
async updateUser(){
|
||||||
|
await this.api.updateUser(this.newUser);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.loading = 'loading';
|
||||||
|
this.api = new AdminAPI(matrix.baseUrl+'/_synapse/admin/', matrix.accessToken);
|
||||||
|
this.users = await this.api.getUsers('users');
|
||||||
|
this.accessDenied = !this.users;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.admin{
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.editIcon{
|
||||||
|
float: left;
|
||||||
|
margin: 0 0.25rem;
|
||||||
|
}
|
||||||
|
.edit{
|
||||||
|
width: 6rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tableScroll{
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
max-width: 80%;
|
||||||
|
background-color: #1f262b;
|
||||||
|
box-shadow: 3px 3px 10px #333;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 2px;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
table-layout: auto;
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
margin: auto;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
margin: auto;
|
||||||
|
background-color: #546E7A;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
margin: auto;
|
||||||
|
background-color: #37474F;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.left, th.left {
|
||||||
|
border-radius: 0.5rem 0 0 0.5rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.right, th.right {
|
||||||
|
border-radius: 0 0.5rem 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.mid, th.mid {
|
||||||
|
border-radius: 0 0 0 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,7 +6,7 @@
|
|||||||
<p v-if="room.timeline.length === 0" class="chatInfo">this room is empty</p>
|
<p v-if="room.timeline.length === 0" class="chatInfo">this room is empty</p>
|
||||||
<timeline :timeline="room.timeline" :group-timeline="isGroup()" :user="user" :roomId="room.roomId" />
|
<timeline :timeline="room.timeline" :group-timeline="isGroup()" :user="user" :roomId="room.roomId" />
|
||||||
</div>
|
</div>
|
||||||
<icon v-if="showScrollBtn" @click.native="scroll.scrollToBottom()" id="scrollDown" ic="./sym/expand_more-black-24dp.svg" />
|
<icon v-if="showScrollBtn" @click.native="scroll.scrollToBottom()" id="scrollDown" ic="./sym/ic_expand_more_black.svg" />
|
||||||
</div>
|
</div>
|
||||||
<newMessage :onResize="height=>resize(height)" :roomId="room.roomId"/>
|
<newMessage :onResize="height=>resize(height)" :roomId="room.roomId"/>
|
||||||
<topBanner :room="room" :close-chat="()=>closeChat()" :open-chat-info="()=>openChatInfo()"/>
|
<topBanner :room="room" :close-chat="()=>closeChat()" :open-chat-info="()=>openChatInfo()"/>
|
||||||
|
@ -108,23 +108,6 @@ export default {
|
|||||||
height: min-content;
|
height: min-content;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
input{
|
|
||||||
padding: 0 2rem 0 2rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1d1d1d;
|
|
||||||
border-radius: 1.25rem;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0.5rem;
|
|
||||||
appearance: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
input:focus{
|
|
||||||
color: #000;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.throbber{
|
.throbber{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
@ -102,19 +102,7 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
input{
|
input{
|
||||||
padding: 0 2rem 0 2rem;
|
width: calc(100% - 5rem);
|
||||||
height: 2.5rem;
|
|
||||||
width: calc(100% - 6rem);
|
|
||||||
left: 1rem;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1d1d1d;
|
|
||||||
border-radius: 1.25rem;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0.5rem;
|
|
||||||
appearance: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
.wideElement{
|
.wideElement{
|
||||||
display: block;
|
display: block;
|
||||||
|