Staff override
This commit is contained in:
parent
ec3b721b40
commit
1a6b50d293
23
app/http/endpoints/api/admin/botstaff/add.go
Normal file
23
app/http/endpoints/api/admin/botstaff/add.go
Normal file
@ -0,0 +1,23 @@
|
||||
package botstaff
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AddBotStaffHandler(ctx *gin.Context) {
|
||||
userId, err := strconv.ParseUint(ctx.Param("userid"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.Client.BotStaff.Add(userId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(204)
|
||||
}
|
57
app/http/endpoints/api/admin/botstaff/list.go
Normal file
57
app/http/endpoints/api/admin/botstaff/list.go
Normal file
@ -0,0 +1,57 @@
|
||||
package botstaff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rxdn/gdl/objects/user"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type userData struct {
|
||||
Id uint64 `json:"id,string"`
|
||||
Username string `json:"username"`
|
||||
Discriminator user.Discriminator `json:"discriminator"`
|
||||
}
|
||||
|
||||
func ListBotStaffHandler(ctx *gin.Context) {
|
||||
staff, err := database.Client.BotStaff.GetAll()
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Get usernames
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
users := make([]userData, len(staff))
|
||||
for i, userId := range staff {
|
||||
i := i
|
||||
userId := userId
|
||||
|
||||
group.Go(func() error {
|
||||
user, ok := cache.Instance.GetUser(userId)
|
||||
|
||||
data := userData{
|
||||
Id: userId,
|
||||
}
|
||||
|
||||
if ok {
|
||||
data.Username = user.Username
|
||||
data.Discriminator = user.Discriminator
|
||||
} else {
|
||||
data.Username = "Unknown User"
|
||||
}
|
||||
|
||||
users[i] = data
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
_ = group.Wait() // error not possible
|
||||
|
||||
ctx.JSON(200, users)
|
||||
}
|
23
app/http/endpoints/api/admin/botstaff/remove.go
Normal file
23
app/http/endpoints/api/admin/botstaff/remove.go
Normal file
@ -0,0 +1,23 @@
|
||||
package botstaff
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RemoveBotStaffHandler(ctx *gin.Context) {
|
||||
userId, err := strconv.ParseUint(ctx.Param("userid"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.Client.BotStaff.Delete(userId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(204)
|
||||
}
|
@ -33,17 +33,12 @@ func SessionHandler(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var whitelabelOverride bool
|
||||
for _, id := range config.Conf.ForceWhitelabel {
|
||||
if id == userId {
|
||||
whitelabelOverride = true
|
||||
break
|
||||
}
|
||||
}
|
||||
whitelabelOverride := utils.Contains(config.Conf.ForceWhitelabel, userId)
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"username": store.Name,
|
||||
"avatar": store.Avatar,
|
||||
"whitelabel": tier >= premium.Whitelabel || whitelabelOverride,
|
||||
"admin": utils.Contains(config.Conf.Admins, userId),
|
||||
})
|
||||
}
|
||||
|
32
app/http/endpoints/api/staffoverride/createoverride.go
Normal file
32
app/http/endpoints/api/staffoverride/createoverride.go
Normal file
@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
type createOverrideBody struct {
|
||||
TimePeriod int `json:"time_period"`
|
||||
}
|
||||
|
||||
func CreateOverrideHandler(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
var body createOverrideBody
|
||||
if err := ctx.BindJSON(&body); err != nil {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid request body"))
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
expires := time.Now().Add(time.Hour * time.Duration(body.TimePeriod))
|
||||
if err := database.Client.StaffOverride.Set(guildId, expires); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(204)
|
||||
}
|
18
app/http/endpoints/api/staffoverride/deleteoverride.go
Normal file
18
app/http/endpoints/api/staffoverride/deleteoverride.go
Normal file
@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func DeleteOverrideHandler(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
if err := database.Client.StaffOverride.Delete(guildId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(204)
|
||||
}
|
21
app/http/endpoints/api/staffoverride/getoverride.go
Normal file
21
app/http/endpoints/api/staffoverride/getoverride.go
Normal file
@ -0,0 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetOverrideHandler(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
hasOverride, err := database.Client.StaffOverride.HasActiveOverride(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"has_override": hasOverride,
|
||||
})
|
||||
}
|
17
app/http/middleware/adminonly.go
Normal file
17
app/http/middleware/adminonly.go
Normal file
@ -0,0 +1,17 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AdminOnly(ctx *gin.Context) {
|
||||
userId := ctx.Keys["userid"].(uint64)
|
||||
|
||||
if !utils.Contains(config.Conf.Admins, userId) {
|
||||
ctx.JSON(401, utils.ErrorStr("Unauthorized"))
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
}
|
@ -42,10 +42,12 @@ func AuthenticateGuild(requiredPermissionLevel permission.PermissionLevel) gin.H
|
||||
if permLevel < requiredPermissionLevel {
|
||||
ctx.JSON(403, utils.ErrorStr("Unauthorized"))
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid guild ID"))
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -46,6 +47,7 @@ func Logging(minLevel sentry.Level) gin.HandlerFunc {
|
||||
"user_id": ctx.Keys["userid"],
|
||||
"request_body": string(requestBody),
|
||||
"response": string(responseBody),
|
||||
"stacktrace": string(debug.Stack()),
|
||||
},
|
||||
Level: level,
|
||||
Message: fmt.Sprintf("HTTP %d on %s %s", statusCode, ctx.Request.Method, ctx.FullPath()),
|
||||
|
@ -2,12 +2,14 @@ package http
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
|
||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/api/admin/botstaff"
|
||||
api_autoclose "github.com/TicketsBot/GoPanel/app/http/endpoints/api/autoclose"
|
||||
api_blacklist "github.com/TicketsBot/GoPanel/app/http/endpoints/api/blacklist"
|
||||
api_customisation "github.com/TicketsBot/GoPanel/app/http/endpoints/api/customisation"
|
||||
api_forms "github.com/TicketsBot/GoPanel/app/http/endpoints/api/forms"
|
||||
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
|
||||
api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings"
|
||||
api_override "github.com/TicketsBot/GoPanel/app/http/endpoints/api/staffoverride"
|
||||
api_tags "github.com/TicketsBot/GoPanel/app/http/endpoints/api/tags"
|
||||
api_team "github.com/TicketsBot/GoPanel/app/http/endpoints/api/team"
|
||||
api_ticket "github.com/TicketsBot/GoPanel/app/http/endpoints/api/ticket"
|
||||
@ -67,8 +69,8 @@ func StartServer() {
|
||||
apiGroup.GET("/session", api.SessionHandler)
|
||||
}
|
||||
|
||||
guildAuthApiAdmin := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Admin))
|
||||
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Support))
|
||||
guildAuthApiAdmin := apiGroup.Group("/:id", middleware.AuthenticateGuild(permission.Admin))
|
||||
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(permission.Support))
|
||||
guildApiNoAuth := apiGroup.Group("/:id", middleware.ParseGuildId)
|
||||
{
|
||||
guildAuthApiSupport.GET("/channels", api.ChannelsHandler)
|
||||
@ -150,6 +152,10 @@ func StartServer() {
|
||||
guildAuthApiAdmin.PUT("/team/:teamid/:snowflake", rl(middleware.RateLimitTypeGuild, 5, time.Second*10), api_team.AddMember)
|
||||
guildAuthApiAdmin.DELETE("/team/:teamid", api_team.DeleteTeam)
|
||||
guildAuthApiAdmin.DELETE("/team/:teamid/:snowflake", rl(middleware.RateLimitTypeGuild, 30, time.Minute), api_team.RemoveMember)
|
||||
|
||||
guildAuthApiAdmin.GET("/staff-override", api_override.GetOverrideHandler)
|
||||
guildAuthApiAdmin.POST("/staff-override", api_override.CreateOverrideHandler)
|
||||
guildAuthApiAdmin.DELETE("/staff-override", api_override.DeleteOverrideHandler)
|
||||
}
|
||||
|
||||
userGroup := router.Group("/user", middleware.AuthenticateToken)
|
||||
@ -173,6 +179,13 @@ func StartServer() {
|
||||
}
|
||||
}
|
||||
|
||||
adminGroup := apiGroup.Group("/admin", middleware.AdminOnly)
|
||||
{
|
||||
adminGroup.GET("/bot-staff", botstaff.ListBotStaffHandler)
|
||||
adminGroup.POST("/bot-staff/:userid", botstaff.AddBotStaffHandler)
|
||||
adminGroup.DELETE("/bot-staff/:userid", botstaff.RemoveBotStaffHandler)
|
||||
}
|
||||
|
||||
if err := router.Run(config.Conf.Server.Host); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
`{#if label !== undefined}
|
||||
{#if label !== undefined}
|
||||
<label class="form-label">{label}</label>
|
||||
{/if}`
|
||||
{/if}
|
||||
|
||||
<div class="multiselect-super">
|
||||
<Select placeholder="Search..." optionIdentifier="id" items={roles}
|
||||
|
107
frontend/src/components/manage/StaffOverrideModal.svelte
Normal file
107
frontend/src/components/manage/StaffOverrideModal.svelte
Normal file
@ -0,0 +1,107 @@
|
||||
<div class="modal" transition:fade>
|
||||
<div class="modal-wrapper">
|
||||
<Card footer="{true}" footerRight="{true}" fill="{false}">
|
||||
<span slot="title">Grant Permissions To Tickets Team</span>
|
||||
|
||||
<div slot="body" class="body-wrapper">
|
||||
Grant permission for
|
||||
<Dropdown bind:value={timePeriod}>
|
||||
<option value="1">1 hour</option>
|
||||
<option value="6">6 hours</option>
|
||||
<option value="24">1 day</option>
|
||||
<option value="72">3 days</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="footer-wrapper">
|
||||
<Button danger={true} on:click={dispatchClose}>Cancel</Button>
|
||||
<div style="">
|
||||
<Button on:click={dispatchConfirm}>Confirm</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop" transition:fade>
|
||||
</div>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown}/>
|
||||
|
||||
<script>
|
||||
import {createEventDispatcher} from 'svelte';
|
||||
import {fade} from 'svelte/transition'
|
||||
import Card from "../Card.svelte";
|
||||
import Button from "../Button.svelte";
|
||||
import Dropdown from "../form/Dropdown.svelte";
|
||||
|
||||
export let guildId;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let timePeriod = "1";
|
||||
|
||||
function dispatchClose() {
|
||||
dispatch('close', {});
|
||||
}
|
||||
|
||||
// Dispatch with data
|
||||
function dispatchConfirm() {
|
||||
dispatch('confirm', {timePeriod: parseInt(timePeriod)});
|
||||
}
|
||||
|
||||
function handleKeydown(e) {
|
||||
if (e.key === "Escape") {
|
||||
dispatchClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 501;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
display: flex;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1280px) {
|
||||
.modal-wrapper {
|
||||
width: 96%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 500;
|
||||
background-color: #000;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.body-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
100
frontend/src/includes/AdminSidebar.svelte
Normal file
100
frontend/src/includes/AdminSidebar.svelte
Normal file
@ -0,0 +1,100 @@
|
||||
<script>
|
||||
import {Navigate} from 'svelte-router-spa'
|
||||
</script>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-container" id="sidebar-nav">
|
||||
<div class="inner">
|
||||
<Navigate to="/admin/bot-staff" styles="sidebar-link">
|
||||
<div class="sidebar-element">
|
||||
<i class="fas fa-user-group sidebar-icon"></i>
|
||||
<span class="sidebar-text">Bot Staff</span>
|
||||
</div>
|
||||
</Navigate>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-container">
|
||||
<div class="sidebar-element">
|
||||
<Navigate to="/" styles="sidebar-link">
|
||||
<i class="sidebar-icon fas fa-home sidebar-icon"></i>
|
||||
<span class="sidebar-text">Home</span>
|
||||
</Navigate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 16.6%;
|
||||
background-color: #272727;
|
||||
float: left;
|
||||
background-size: cover;
|
||||
overflow-x: hidden !important;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
|
||||
.inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
padding: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.sidebar-element:hover {
|
||||
background-color: #121212;
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
#custom-image {
|
||||
max-height: 70px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.sidebar-text {
|
||||
margin-left: 4%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#sidebar-nav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.sidebar {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
|
||||
height: unset;
|
||||
min-width: unset;
|
||||
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-element {
|
||||
width: unset;
|
||||
padding: 20px 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -5,6 +5,7 @@
|
||||
export let avatar;
|
||||
|
||||
export let isWhitelabel = false;
|
||||
export let isAdmin = false;
|
||||
</script>
|
||||
|
||||
<div class="sidebar">
|
||||
@ -31,6 +32,14 @@
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
{#if isAdmin}
|
||||
<Navigate to="/admin/bot-staff" styles="sidebar-link">
|
||||
<div class="sidebar-element">
|
||||
<i class="fa-solid fa-user-secret sidebar-icon"></i>
|
||||
<span class="sidebar-text">Admin</span>
|
||||
</div>
|
||||
</Navigate>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-container">
|
||||
|
93
frontend/src/layouts/AdminLayout.svelte
Normal file
93
frontend/src/layouts/AdminLayout.svelte
Normal file
@ -0,0 +1,93 @@
|
||||
<Head/>
|
||||
|
||||
<div class="wrapper">
|
||||
<AdminSidebar />
|
||||
<div class="super-container">
|
||||
<LoadingScreen/>
|
||||
<NotifyModal/>
|
||||
<div class="content-container" class:hide={$loadingScreen}>
|
||||
<Route {currentRoute} {params}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import {navigateTo, Route} from 'svelte-router-spa'
|
||||
import Head from '../includes/Head.svelte'
|
||||
import Sidebar from '../includes/Sidebar.svelte'
|
||||
import LoadingScreen from '../includes/LoadingScreen.svelte'
|
||||
import NotifyModal from '../includes/NotifyModal.svelte'
|
||||
import axios from "axios";
|
||||
import {API_URL} from '../js/constants'
|
||||
import {notifyError} from '../js/util'
|
||||
import {loadingScreen} from "../js/stores"
|
||||
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
|
||||
import AdminSidebar from "../includes/AdminSidebar.svelte";
|
||||
|
||||
export let currentRoute;
|
||||
export let params = {};
|
||||
|
||||
setDefaultHeaders()
|
||||
|
||||
let name;
|
||||
let avatar;
|
||||
|
||||
let isWhitelabel = false;
|
||||
let isAdmin = false;
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get(`${API_URL}/api/session`);
|
||||
if (res.status !== 200) {
|
||||
if (res.data.auth === true) {
|
||||
redirectLogin();
|
||||
}
|
||||
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
isAdmin = res.data.admin;
|
||||
|
||||
if (!isAdmin) {
|
||||
navigateTo(`/`);
|
||||
}
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.super-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<Head/>
|
||||
|
||||
<div class="wrapper">
|
||||
<Sidebar name="{name}" avatar="{avatar}" {isWhitelabel} />
|
||||
<Sidebar {name} {avatar} {isWhitelabel} {isAdmin} />
|
||||
<div class="super-container">
|
||||
<LoadingScreen/>
|
||||
<NotifyModal/>
|
||||
@ -32,6 +32,7 @@
|
||||
let avatar;
|
||||
|
||||
let isWhitelabel = false;
|
||||
let isAdmin = false;
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get(`${API_URL}/api/session`);
|
||||
@ -47,6 +48,7 @@
|
||||
name = res.data.username;
|
||||
avatar = res.data.avatar;
|
||||
isWhitelabel = res.data.whitelabel;
|
||||
isAdmin = res.data.admin;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
@ -2,6 +2,7 @@ import IndexLayout from './layouts/IndexLayout.svelte'
|
||||
import ManageLayout from './layouts/ManageLayout.svelte'
|
||||
import ErrorLayout from './layouts/ErrorPage.svelte'
|
||||
import TranscriptViewLayout from './layouts/TranscriptViewLayout.svelte'
|
||||
import AdminLayout from './layouts/AdminLayout.svelte';
|
||||
|
||||
import Index from './views/Index.svelte'
|
||||
import LoginCallback from './views/LoginCallback.svelte'
|
||||
@ -21,6 +22,8 @@ import Tickets from './views/Tickets.svelte'
|
||||
import TicketView from './views/TicketView.svelte'
|
||||
import Appearance from './views/Appearance.svelte';
|
||||
import Forms from './views/Forms.svelte';
|
||||
import StaffOverride from './views/StaffOverride.svelte';
|
||||
import BotStaff from './views/admin/BotStaff.svelte';
|
||||
|
||||
export const routes = [
|
||||
{name: '/', component: Index, layout: IndexLayout},
|
||||
@ -30,6 +33,12 @@ export const routes = [
|
||||
{name: '/logout', component: Logout},
|
||||
{name: '/error', component: Error, layout: ErrorLayout},
|
||||
{name: '/whitelabel', component: Whitelabel, layout: IndexLayout},
|
||||
{
|
||||
name: 'admin',
|
||||
nestedRoutes: [
|
||||
{name: 'bot-staff', component: BotStaff, layout: AdminLayout},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'manage/:id',
|
||||
nestedRoutes: [
|
||||
@ -67,6 +76,7 @@ export const routes = [
|
||||
{name: 'tags', component: Tags, layout: ManageLayout},
|
||||
{name: 'teams', component: Teams, layout: ManageLayout},
|
||||
{name: 'forms', component: Forms, layout: ManageLayout},
|
||||
{name: 'staffoverride', component: StaffOverride, layout: ManageLayout},
|
||||
{
|
||||
name: 'tickets',
|
||||
nestedRoutes: [
|
||||
|
134
frontend/src/views/StaffOverride.svelte
Normal file
134
frontend/src/views/StaffOverride.svelte
Normal file
@ -0,0 +1,134 @@
|
||||
{#if modal}
|
||||
<StaffOverrideModal {guildId} on:close={() => modal = false} on:confirm={handleConfirm}/>
|
||||
{/if}
|
||||
|
||||
<div class="parent">
|
||||
<div class="content">
|
||||
<div class="main-col">
|
||||
<Card footer footerRight>
|
||||
<span slot="title">Staff Override</span>
|
||||
<div slot="body" class="body-wrapper">
|
||||
You can grant access to the Tickets support team to temporarily access the dashboard for your server to help
|
||||
you resolve issues. You can revoke access at any time by visiting this page.
|
||||
</div>
|
||||
<div slot="footer" class="footer-wrapper">
|
||||
{#if activeOverride}
|
||||
<Button danger on:click={removeOverride}>Revoke Access</Button>
|
||||
{/if}
|
||||
<Button on:click={() => modal = true}>Grant Access</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import Card from "../components/Card.svelte";
|
||||
import {notifyError, notifySuccess, withLoadingScreen} from '../js/util'
|
||||
import axios from "axios";
|
||||
import {API_URL} from "../js/constants";
|
||||
import {setDefaultHeaders} from '../includes/Auth.svelte'
|
||||
import Button from "../components/Button.svelte";
|
||||
import StaffOverrideModal from "../components/manage/StaffOverrideModal.svelte";
|
||||
|
||||
export let currentRoute;
|
||||
let guildId = currentRoute.namedParams.id;
|
||||
|
||||
let modal = false;
|
||||
let activeOverride = false;
|
||||
|
||||
async function handleConfirm(e) {
|
||||
await createOverride(e.detail.timePeriod);
|
||||
}
|
||||
|
||||
async function loadActiveOverride() {
|
||||
const res = await axios.get(`${API_URL}/api/${guildId}/staff-override`);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
activeOverride = res.data.has_override;
|
||||
}
|
||||
|
||||
async function createOverride(timePeriod) {
|
||||
let data = {
|
||||
time_period: timePeriod
|
||||
};
|
||||
|
||||
const res = await axios.post(`${API_URL}/api/${guildId}/staff-override`, data);
|
||||
if (res.status !== 204) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
modal = false;
|
||||
activeOverride = true;
|
||||
notifySuccess('Staff access override has been granted');
|
||||
}
|
||||
|
||||
async function removeOverride() {
|
||||
const res = await axios.delete(`${API_URL}/api/${guildId}/staff-override`);
|
||||
if (res.status !== 204) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
activeOverride = false;
|
||||
notifySuccess('Staff access override has been revoked');
|
||||
}
|
||||
|
||||
withLoadingScreen(async () => {
|
||||
setDefaultHeaders();
|
||||
await loadActiveOverride();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.parent {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 96%;
|
||||
height: 100%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.main-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 64%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.body-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 950px) {
|
||||
.content {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.main-col {
|
||||
width: 100%;
|
||||
margin-top: 4%;
|
||||
}
|
||||
}
|
||||
</style>
|
127
frontend/src/views/admin/BotStaff.svelte
Normal file
127
frontend/src/views/admin/BotStaff.svelte
Normal file
@ -0,0 +1,127 @@
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<Card footer="{false}" fill="{false}">
|
||||
<h4 slot="title">Bot Staff</h4>
|
||||
<div slot="body" class="full-width body-wrapper">
|
||||
<form class="form-wrapper" on:submit|preventDefault={addStaff}>
|
||||
<Input label="User ID" placeholder="585576154958921739" bind:value={tempUserId} />
|
||||
<Button type="submit">Add</Button>
|
||||
</form>
|
||||
|
||||
<table class="nice">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each staff as user}
|
||||
<tr>
|
||||
<td>{user.username}#{user.discriminator} ({user.id})</td>
|
||||
<td>
|
||||
<Button type="button" danger on:click={() => removeStaff(user.id)}>
|
||||
Delete
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import {notifyError, withLoadingScreen} from '../../js/util'
|
||||
import axios from "axios";
|
||||
import Card from '../../components/Card.svelte'
|
||||
import {API_URL} from "../../js/constants";
|
||||
import {setDefaultHeaders} from '../../includes/Auth.svelte'
|
||||
import Button from "../../components/Button.svelte";
|
||||
import Input from "../../components/form/Input.svelte";
|
||||
|
||||
setDefaultHeaders()
|
||||
|
||||
let staff = [];
|
||||
let tempUserId = "";
|
||||
|
||||
async function addStaff() {
|
||||
if (tempUserId.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await axios.post(`${API_URL}/api/admin/bot-staff/${tempUserId}`);
|
||||
if (res.status !== 204) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
tempUserId = "";
|
||||
await loadData(); // TODO: Return user data with response
|
||||
}
|
||||
|
||||
async function removeStaff(userId) {
|
||||
const res = await axios.delete(`${API_URL}/api/admin/bot-staff/${userId}`);
|
||||
if (res.status !== 204) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
staff = staff.filter(user => user.id !== userId);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get(`${API_URL}/api/admin/bot-staff`);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
staff = res.data;
|
||||
}
|
||||
|
||||
withLoadingScreen(async () => {
|
||||
await loadData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: row;
|
||||
width: 95%;
|
||||
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
.content {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.body-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
|
||||
github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a
|
||||
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de
|
||||
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90
|
||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
|
||||
github.com/TicketsBot/worker v0.0.0-20220621165800-203b0004b733
|
||||
github.com/apex/log v1.1.2
|
||||
|
2
go.sum
2
go.sum
@ -7,6 +7,8 @@ github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a h1:SwA18cDURmnXS
|
||||
github.com/TicketsBot/common v0.0.0-20220615205931-a6a31e73b52a/go.mod h1:ZAoYcDD7SQLTsZT7dbo/X0J256+pogVRAReunCGng+U=
|
||||
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de h1:UsRiB3KIwqIF92huRBFKAnCoGLyT9kBYYUycsapBZk0=
|
||||
github.com/TicketsBot/database v0.0.0-20220621182433-accd1b2b81de/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
|
||||
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90 h1:K0t6IaZdeZzEr2BaYj/NBuWIm/hA31jkqFh2c3nyDrw=
|
||||
github.com/TicketsBot/database v0.0.0-20220623160906-a56dc9dfda90/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
|
||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c h1:OqGjFH6mbE6gd+NqI2ARJdtH3UUvhiAkD0r0fhGJK2s=
|
||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c/go.mod h1:jgi2OXQKsd5nUnTIRkwvPmeuD/i7OhN68LKMssuQY1c=
|
||||
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=
|
||||
|
@ -20,6 +20,24 @@ func GetPermissionLevel(guildId, userId uint64) (permission.PermissionLevel, err
|
||||
return permission.Admin, nil
|
||||
}
|
||||
|
||||
// Check staff override
|
||||
staffOverride, err := dbclient.Client.StaffOverride.HasActiveOverride(guildId)
|
||||
if err != nil {
|
||||
return permission.Everyone, err
|
||||
}
|
||||
|
||||
// If staff override enabled and the user is bot staff, grant admin permissions
|
||||
if staffOverride {
|
||||
isBotStaff, err := dbclient.Client.BotStaff.IsStaff(userId)
|
||||
if err != nil {
|
||||
return permission.Everyone, err
|
||||
}
|
||||
|
||||
if isBotStaff {
|
||||
return permission.Admin, nil
|
||||
}
|
||||
}
|
||||
|
||||
// get member
|
||||
member, err := botContext.GetGuildMember(guildId, userId)
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user