Revamp settings page
This commit is contained in:
parent
35658b2aaf
commit
59f4a247db
@ -1,67 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/database"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// time.Duration marshals to nanoseconds, custom impl to marshal to seconds
|
|
||||||
type autoCloseBody struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
SinceOpenWithNoResponse int64 `json:"since_open_with_no_response"`
|
|
||||||
SinceLastMessage int64 `json:"since_last_message"`
|
|
||||||
OnUserLeave bool `json:"on_user_leave"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAutoClose(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
settings, err := dbclient.Client.AutoClose.Get(guildId)
|
|
||||||
if err != nil {
|
|
||||||
ctx.AbortWithStatusJSON(500, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, convertToAutoCloseBody(settings))
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToAutoCloseBody(settings database.AutoCloseSettings) (body autoCloseBody) {
|
|
||||||
body.Enabled = settings.Enabled
|
|
||||||
|
|
||||||
if settings.SinceOpenWithNoResponse != nil {
|
|
||||||
body.SinceOpenWithNoResponse = int64(*settings.SinceOpenWithNoResponse / time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.SinceLastMessage != nil {
|
|
||||||
body.SinceLastMessage = int64(*settings.SinceLastMessage / time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.OnUserLeave != nil {
|
|
||||||
body.OnUserLeave = *settings.OnUserLeave
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertFromAutoCloseBody(body autoCloseBody) (settings database.AutoCloseSettings) {
|
|
||||||
settings.Enabled = body.Enabled
|
|
||||||
|
|
||||||
if body.SinceOpenWithNoResponse > 0 {
|
|
||||||
duration := time.Second * time.Duration(body.SinceOpenWithNoResponse)
|
|
||||||
settings.SinceOpenWithNoResponse = &duration
|
|
||||||
}
|
|
||||||
|
|
||||||
if body.SinceLastMessage > 0 {
|
|
||||||
duration := time.Second * time.Duration(body.SinceLastMessage)
|
|
||||||
settings.SinceLastMessage = &duration
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.OnUserLeave = &body.OnUserLeave
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TicketsBot/GoPanel/botcontext"
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/GoPanel/rpc"
|
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
|
||||||
"github.com/TicketsBot/common/premium"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var maxDays = 90
|
|
||||||
var maxLength = time.Hour * 24 * time.Duration(maxDays)
|
|
||||||
|
|
||||||
func PostAutoClose(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
var body autoCloseBody
|
|
||||||
if err := ctx.BindJSON(&body); err != nil {
|
|
||||||
ctx.JSON(400, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := convertFromAutoCloseBody(body)
|
|
||||||
|
|
||||||
// get premium
|
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
premiumTier, err := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if premiumTier < premium.Premium {
|
|
||||||
settings.SinceOpenWithNoResponse = nil
|
|
||||||
settings.SinceLastMessage = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time period cannot be negative, convertFromAutoCloseBody will not allow
|
|
||||||
if (settings.SinceOpenWithNoResponse != nil && *settings.SinceOpenWithNoResponse > maxLength) ||
|
|
||||||
(settings.SinceLastMessage != nil && *settings.SinceLastMessage > maxLength) {
|
|
||||||
ctx.JSON(400, utils.ErrorStr("Time period cannot be longer than %d days", maxDays))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !settings.Enabled {
|
|
||||||
settings.SinceLastMessage = nil
|
|
||||||
settings.SinceOpenWithNoResponse = nil
|
|
||||||
settings.OnUserLeave = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbclient.Client.AutoClose.Set(guildId, settings); err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, utils.SuccessResponse)
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package customisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
|
||||||
"github.com/TicketsBot/worker/bot/customisation"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetColours TODO: Don't depend on worker
|
|
||||||
func GetColours(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
// TODO: Don't duplicate
|
|
||||||
raw, err := dbclient.Client.CustomColours.GetAll(guildId)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
colours := make(map[customisation.Colour]utils.HexColour)
|
|
||||||
for id, hex := range raw {
|
|
||||||
colours[customisation.Colour(id)] = utils.HexColour(hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, hex := range customisation.DefaultColours {
|
|
||||||
if _, ok := colours[id]; !ok {
|
|
||||||
colours[id] = utils.HexColour(hex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, colours)
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package customisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/TicketsBot/GoPanel/botcontext"
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/GoPanel/rpc"
|
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
|
||||||
"github.com/TicketsBot/common/premium"
|
|
||||||
"github.com/TicketsBot/worker/bot/customisation"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UpdateColours TODO: Don't depend on worker
|
|
||||||
func UpdateColours(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow votes
|
|
||||||
premiumTier, err := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if premiumTier < premium.Premium {
|
|
||||||
ctx.JSON(402, utils.ErrorStr("You must have premium to customise message appearance"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data map[customisation.Colour]utils.HexColour
|
|
||||||
if err := ctx.BindJSON(&data); err != nil {
|
|
||||||
ctx.JSON(400, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) > len(customisation.DefaultColours) {
|
|
||||||
ctx.JSON(400, utils.ErrorStr("Invalid colour"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for colourCode, hex := range customisation.DefaultColours {
|
|
||||||
if _, ok := data[colourCode]; !ok {
|
|
||||||
data[colourCode] = utils.HexColour(hex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Single query
|
|
||||||
group, _ := errgroup.WithContext(context.Background())
|
|
||||||
for colourCode, hex := range data {
|
|
||||||
colourCode := colourCode
|
|
||||||
hex := hex
|
|
||||||
|
|
||||||
group.Go(func() error {
|
|
||||||
return dbclient.Client.CustomColours.Set(guildId, colourCode.Int16(), hex.Int())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, utils.SuccessResponse)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetClaimSettings(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
settings, err := database.Client.ClaimSettings.Get(guildId); if err != nil {
|
|
||||||
ctx.AbortWithStatusJSON(500, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, settings)
|
|
||||||
}
|
|
@ -3,24 +3,44 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
"github.com/TicketsBot/database"
|
"github.com/TicketsBot/database"
|
||||||
|
"github.com/TicketsBot/worker/bot/customisation"
|
||||||
|
"github.com/TicketsBot/worker/i18n"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type (
|
||||||
database.Settings
|
Settings struct {
|
||||||
Prefix string `json:"prefix"`
|
database.Settings
|
||||||
WelcomeMessage string `json:"welcome_message"`
|
ClaimSettings database.ClaimSettings `json:"claim_settings"`
|
||||||
TicketLimit uint8 `json:"ticket_limit"`
|
AutoCloseSettings AutoCloseData `json:"auto_close"`
|
||||||
Category uint64 `json:"category,string"`
|
Colours ColourMap `json:"colours"`
|
||||||
ArchiveChannel *uint64 `json:"archive_channel,string"`
|
|
||||||
NamingScheme database.NamingScheme `json:"naming_scheme"`
|
Prefix string `json:"prefix"`
|
||||||
PingEveryone bool `json:"ping_everyone"`
|
WelcomeMessage string `json:"welcome_message"`
|
||||||
UsersCanClose bool `json:"users_can_close"`
|
TicketLimit uint8 `json:"ticket_limit"`
|
||||||
CloseConfirmation bool `json:"close_confirmation"`
|
Category uint64 `json:"category,string"`
|
||||||
FeedbackEnabled bool `json:"feedback_enabled"`
|
ArchiveChannel *uint64 `json:"archive_channel,string"`
|
||||||
}
|
NamingScheme database.NamingScheme `json:"naming_scheme"`
|
||||||
|
PingEveryone bool `json:"ping_everyone"`
|
||||||
|
UsersCanClose bool `json:"users_can_close"`
|
||||||
|
CloseConfirmation bool `json:"close_confirmation"`
|
||||||
|
FeedbackEnabled bool `json:"feedback_enabled"`
|
||||||
|
Language *i18n.Language `json:"language"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoCloseData struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
SinceOpenWithNoResponse int64 `json:"since_open_with_no_response"`
|
||||||
|
SinceLastMessage int64 `json:"since_last_message"`
|
||||||
|
OnUserLeave bool `json:"on_user_leave"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ColourMap map[customisation.Colour]utils.HexColour
|
||||||
|
)
|
||||||
|
|
||||||
func GetSettingsHandler(ctx *gin.Context) {
|
func GetSettingsHandler(ctx *gin.Context) {
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
@ -29,11 +49,35 @@ func GetSettingsHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
group, _ := errgroup.WithContext(context.Background())
|
group, _ := errgroup.WithContext(context.Background())
|
||||||
|
|
||||||
|
// main settings
|
||||||
group.Go(func() (err error) {
|
group.Go(func() (err error) {
|
||||||
settings.Settings, err = dbclient.Client.Settings.Get(guildId)
|
settings.Settings, err = dbclient.Client.Settings.Get(guildId)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// claim settings
|
||||||
|
group.Go(func() (err error) {
|
||||||
|
settings.ClaimSettings, err = dbclient.Client.ClaimSettings.Get(guildId)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
// auto close settings
|
||||||
|
group.Go(func() error {
|
||||||
|
tmp, err := dbclient.Client.AutoClose.Get(guildId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.AutoCloseSettings = convertToAutoCloseData(tmp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// colour map
|
||||||
|
group.Go(func() (err error) {
|
||||||
|
settings.Colours, err = getColourMap(guildId)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
// prefix
|
// prefix
|
||||||
group.Go(func() (err error) {
|
group.Go(func() (err error) {
|
||||||
settings.Prefix, err = dbclient.Client.Prefix.Get(guildId)
|
settings.Prefix, err = dbclient.Client.Prefix.Get(guildId)
|
||||||
@ -106,13 +150,74 @@ func GetSettingsHandler(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// language
|
||||||
|
group.Go(func() error {
|
||||||
|
tmp, err := dbclient.Client.ActiveLanguage.Get(guildId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp != "" {
|
||||||
|
settings.Language = utils.Ptr(i18n.Language(tmp))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, gin.H{
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(200, settings)
|
ctx.JSON(200, struct {
|
||||||
|
Settings
|
||||||
|
Languages []i18n.Language `json:"languages"`
|
||||||
|
LanguageNames map[i18n.Language]string `json:"language_names"`
|
||||||
|
}{
|
||||||
|
Settings: settings,
|
||||||
|
Languages: i18n.LanguagesAlphabetical,
|
||||||
|
LanguageNames: i18n.FullNames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getColourMap(guildId uint64) (ColourMap, error) {
|
||||||
|
raw, err := dbclient.Client.CustomColours.GetAll(guildId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
colours := make(ColourMap)
|
||||||
|
for id, hex := range raw {
|
||||||
|
if !utils.Exists(activeColours, customisation.Colour(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
colours[customisation.Colour(id)] = utils.HexColour(hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range activeColours {
|
||||||
|
if _, ok := colours[id]; !ok {
|
||||||
|
colours[id] = utils.HexColour(customisation.DefaultColours[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colours, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToAutoCloseData(settings database.AutoCloseSettings) (body AutoCloseData) {
|
||||||
|
body.Enabled = settings.Enabled
|
||||||
|
|
||||||
|
if settings.SinceOpenWithNoResponse != nil {
|
||||||
|
body.SinceOpenWithNoResponse = int64(*settings.SinceOpenWithNoResponse / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.SinceLastMessage != nil {
|
||||||
|
body.SinceLastMessage = int64(*settings.SinceLastMessage / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.OnUserLeave != nil {
|
||||||
|
body.OnUserLeave = *settings.OnUserLeave
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/database"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PostClaimSettings(ctx *gin.Context) {
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
|
||||||
|
|
||||||
var settings database.ClaimSettings
|
|
||||||
if err := ctx.BindJSON(&settings); err != nil {
|
|
||||||
ctx.AbortWithStatusJSON(400, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.SupportCanType && !settings.SupportCanView {
|
|
||||||
ctx.AbortWithStatusJSON(400, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"error": "Must be able to view channel to type",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbclient.Client.ClaimSettings.Set(guildId, settings); err != nil {
|
|
||||||
ctx.AbortWithStatusJSON(500, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, gin.H{
|
|
||||||
"success": true,
|
|
||||||
})
|
|
||||||
}
|
|
@ -2,14 +2,21 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/botcontext"
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||||
|
"github.com/TicketsBot/GoPanel/rpc"
|
||||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
|
"github.com/TicketsBot/common/premium"
|
||||||
"github.com/TicketsBot/database"
|
"github.com/TicketsBot/database"
|
||||||
|
"github.com/TicketsBot/worker/bot/customisation"
|
||||||
|
"github.com/TicketsBot/worker/i18n"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rxdn/gdl/objects/channel"
|
"github.com/rxdn/gdl/objects/channel"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateSettingsHandler(ctx *gin.Context) {
|
func UpdateSettingsHandler(ctx *gin.Context) {
|
||||||
@ -17,20 +24,52 @@ func UpdateSettingsHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
var settings Settings
|
var settings Settings
|
||||||
if err := ctx.BindJSON(&settings); err != nil {
|
if err := ctx.BindJSON(&settings); err != nil {
|
||||||
ctx.AbortWithStatusJSON(400, gin.H{
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
"success": false,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of all channel IDs
|
// Get a list of all channel IDs
|
||||||
channels := cache.Instance.GetGuildChannels(guildId)
|
channels := cache.Instance.GetGuildChannels(guildId)
|
||||||
|
|
||||||
|
botContext, err := botcontext.ContextForGuild(guildId)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Includes voting
|
||||||
|
premiumTier, err := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := settings.Validate(guildId, premiumTier); err != nil {
|
||||||
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
group, _ := errgroup.WithContext(context.Background())
|
||||||
|
|
||||||
|
group.Go(func() error {
|
||||||
|
return settings.updateSettings(guildId)
|
||||||
|
})
|
||||||
|
|
||||||
|
group.Go(func() error {
|
||||||
|
return settings.updateClaimSettings(guildId)
|
||||||
|
})
|
||||||
|
|
||||||
|
addToWaitGroup(group, guildId, settings.updateLanguage)
|
||||||
|
addToWaitGroup(group, guildId, settings.updateAutoClose)
|
||||||
|
|
||||||
|
if premiumTier > premium.None {
|
||||||
|
addToWaitGroup(group, guildId, settings.updateColours)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Errors
|
// TODO: Errors
|
||||||
var errStr *string = nil
|
var errStr *string = nil
|
||||||
if e := settings.updateSettings(guildId); e != nil {
|
if err := group.Wait(); err != nil {
|
||||||
errStr = utils.Ptr(e.Error())
|
errStr = utils.Ptr(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
validPrefix := settings.updatePrefix(guildId)
|
validPrefix := settings.updatePrefix(guildId)
|
||||||
@ -56,16 +95,77 @@ func UpdateSettingsHandler(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) updateSettings(guildId uint64) error {
|
func (s *Settings) updateSettings(guildId uint64) error {
|
||||||
if err := s.Validate(guildId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbclient.Client.Settings.Set(guildId, s.Settings)
|
return dbclient.Client.Settings.Set(guildId, s.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
var validAutoArchive = []int{60, 1440, 4320, 10080}
|
func (s *Settings) updateClaimSettings(guildId uint64) error {
|
||||||
|
return dbclient.Client.ClaimSettings.Set(guildId, s.ClaimSettings)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Settings) Validate(guildId uint64) error {
|
var (
|
||||||
|
validAutoArchive = []int{60, 1440, 4320, 10080}
|
||||||
|
activeColours = []customisation.Colour{customisation.Green, customisation.Red}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Settings) Validate(guildId uint64, premiumTier premium.PremiumTier) error {
|
||||||
|
// Sync checks
|
||||||
|
if s.ClaimSettings.SupportCanType && !s.ClaimSettings.SupportCanView {
|
||||||
|
return errors.New("Must be able to view channel to type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Settings.UseThreads {
|
||||||
|
return fmt.Errorf("threads are disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Language != nil {
|
||||||
|
if _, ok := i18n.FullNames[*s.Language]; !ok {
|
||||||
|
return fmt.Errorf("invalid language")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate colours
|
||||||
|
if len(s.Colours) > len(activeColours) {
|
||||||
|
return errors.New("Invalid colour")
|
||||||
|
}
|
||||||
|
|
||||||
|
for colour, _ := range s.Colours {
|
||||||
|
if !utils.Exists(activeColours, colour) {
|
||||||
|
return errors.New("Invalid colour")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, colourCode := range activeColours {
|
||||||
|
if _, ok := s.Colours[colourCode]; !ok {
|
||||||
|
s.Colours[colourCode] = utils.HexColour(customisation.DefaultColours[colourCode])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate autoclose
|
||||||
|
if premiumTier < premium.Premium {
|
||||||
|
s.AutoCloseSettings.SinceOpenWithNoResponse = 0
|
||||||
|
s.AutoCloseSettings.SinceLastMessage = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.AutoCloseSettings.Enabled {
|
||||||
|
s.AutoCloseSettings.SinceOpenWithNoResponse = 0
|
||||||
|
s.AutoCloseSettings.SinceLastMessage = 0
|
||||||
|
s.AutoCloseSettings.OnUserLeave = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AutoCloseSettings.SinceOpenWithNoResponse < 0 {
|
||||||
|
s.AutoCloseSettings.SinceOpenWithNoResponse = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AutoCloseSettings.SinceLastMessage < 0 {
|
||||||
|
s.AutoCloseSettings.SinceLastMessage = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AutoCloseSettings.SinceLastMessage > int64((time.Hour*24*60).Seconds()) ||
|
||||||
|
s.AutoCloseSettings.SinceOpenWithNoResponse > int64((time.Hour*24*60).Seconds()) {
|
||||||
|
return errors.New("Autoclose time period cannot be longer than 60 days")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async checks
|
||||||
group, _ := errgroup.WithContext(context.Background())
|
group, _ := errgroup.WithContext(context.Background())
|
||||||
|
|
||||||
// Validate panel from same guild
|
// Validate panel from same guild
|
||||||
@ -102,14 +202,6 @@ func (s *Settings) Validate(guildId uint64) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
group.Go(func() error {
|
|
||||||
if s.Settings.UseThreads {
|
|
||||||
return fmt.Errorf("threads are disabled")
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
group.Go(func() error {
|
group.Go(func() error {
|
||||||
if s.Settings.OverflowCategoryId != nil {
|
if s.Settings.OverflowCategoryId != nil {
|
||||||
ch, ok := cache.Instance.GetChannel(*s.Settings.OverflowCategoryId)
|
ch, ok := cache.Instance.GetChannel(*s.Settings.OverflowCategoryId)
|
||||||
@ -132,6 +224,12 @@ func (s *Settings) Validate(guildId uint64) error {
|
|||||||
return group.Wait()
|
return group.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToWaitGroup(group *errgroup.Group, guildId uint64, f func(uint64) error) {
|
||||||
|
group.Go(func() error {
|
||||||
|
return f(guildId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Settings) updatePrefix(guildId uint64) bool {
|
func (s *Settings) updatePrefix(guildId uint64) bool {
|
||||||
if s.Prefix == "" || len(s.Prefix) > 8 {
|
if s.Prefix == "" || len(s.Prefix) > 8 {
|
||||||
return false
|
return false
|
||||||
@ -232,3 +330,43 @@ func (s *Settings) updateCloseConfirmation(guildId uint64) {
|
|||||||
func (s *Settings) updateFeedbackEnabled(guildId uint64) {
|
func (s *Settings) updateFeedbackEnabled(guildId uint64) {
|
||||||
go dbclient.Client.FeedbackEnabled.Set(guildId, s.FeedbackEnabled)
|
go dbclient.Client.FeedbackEnabled.Set(guildId, s.FeedbackEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Settings) updateLanguage(guildId uint64) error {
|
||||||
|
if s.Language == nil {
|
||||||
|
return dbclient.Client.ActiveLanguage.Delete(guildId)
|
||||||
|
} else {
|
||||||
|
return dbclient.Client.ActiveLanguage.Set(guildId, string(*s.Language))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) updateColours(guildId uint64) error {
|
||||||
|
// Convert ColourMap to primitives
|
||||||
|
converted := make(map[int16]int)
|
||||||
|
for colour, hex := range s.Colours {
|
||||||
|
converted[int16(colour)] = int(hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbclient.Client.CustomColours.BatchSet(guildId, converted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) updateAutoClose(guildId uint64) error {
|
||||||
|
data := s.AutoCloseSettings.ConvertToDatabase() // Already validated
|
||||||
|
return dbclient.Client.AutoClose.Set(guildId, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d AutoCloseData) ConvertToDatabase() (settings database.AutoCloseSettings) {
|
||||||
|
settings.Enabled = d.Enabled
|
||||||
|
|
||||||
|
if d.SinceOpenWithNoResponse > 0 {
|
||||||
|
duration := time.Second * time.Duration(d.SinceOpenWithNoResponse)
|
||||||
|
settings.SinceOpenWithNoResponse = &duration
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.SinceLastMessage > 0 {
|
||||||
|
duration := time.Second * time.Duration(d.SinceLastMessage)
|
||||||
|
settings.SinceLastMessage = &duration
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.OnUserLeave = &d.OnUserLeave
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -3,9 +3,7 @@ package http
|
|||||||
import (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
|
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
|
||||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/api/admin/botstaff"
|
"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_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_forms "github.com/TicketsBot/GoPanel/app/http/endpoints/api/forms"
|
||||||
api_integrations "github.com/TicketsBot/GoPanel/app/http/endpoints/api/integrations"
|
api_integrations "github.com/TicketsBot/GoPanel/app/http/endpoints/api/integrations"
|
||||||
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
|
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
|
||||||
@ -149,15 +147,6 @@ func StartServer() {
|
|||||||
guildAuthApiSupport.PUT("/tags", api_tags.CreateTag)
|
guildAuthApiSupport.PUT("/tags", api_tags.CreateTag)
|
||||||
guildAuthApiSupport.DELETE("/tags", api_tags.DeleteTag)
|
guildAuthApiSupport.DELETE("/tags", api_tags.DeleteTag)
|
||||||
|
|
||||||
guildAuthApiAdmin.GET("/claimsettings", api_settings.GetClaimSettings)
|
|
||||||
guildAuthApiAdmin.POST("/claimsettings", api_settings.PostClaimSettings)
|
|
||||||
|
|
||||||
guildAuthApiAdmin.GET("/autoclose", api_autoclose.GetAutoClose)
|
|
||||||
guildAuthApiAdmin.POST("/autoclose", api_autoclose.PostAutoClose)
|
|
||||||
|
|
||||||
guildAuthApiAdmin.GET("/customisation/colours", api_customisation.GetColours)
|
|
||||||
guildAuthApiAdmin.POST("/customisation/colours", api_customisation.UpdateColours)
|
|
||||||
|
|
||||||
guildAuthApiAdmin.GET("/team", api_team.GetTeams)
|
guildAuthApiAdmin.GET("/team", api_team.GetTeams)
|
||||||
guildAuthApiAdmin.GET("/team/:teamid", rl(middleware.RateLimitTypeUser, 10, time.Second*30), api_team.GetMembers)
|
guildAuthApiAdmin.GET("/team/:teamid", rl(middleware.RateLimitTypeUser, 10, time.Second*30), api_team.GetMembers)
|
||||||
guildAuthApiAdmin.POST("/team", rl(middleware.RateLimitTypeUser, 10, time.Minute), api_team.CreateTeam)
|
guildAuthApiAdmin.POST("/team", rl(middleware.RateLimitTypeUser, 10, time.Minute), api_team.CreateTeam)
|
||||||
|
@ -16,17 +16,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<svelte:window bind:innerWidth />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
export let retractIcon = "fas fa-minus";
|
export let retractIcon = "fas fa-minus";
|
||||||
export let expandIcon = "fas fa-plus";
|
export let expandIcon = "fas fa-plus";
|
||||||
|
|
||||||
|
export let defaultOpen = false;
|
||||||
|
|
||||||
let expanded = false;
|
let expanded = false;
|
||||||
let showOverflow = true;
|
let showOverflow = true;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
|
let innerWidth;
|
||||||
|
$: innerWidth, updateIfExpanded();
|
||||||
|
|
||||||
export function toggle() {
|
export function toggle() {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
content.style.maxHeight = 0;
|
content.style.maxHeight = 0;
|
||||||
@ -41,15 +48,17 @@
|
|||||||
content.style.maxHeight = `${content.scrollHeight}px`;
|
content.style.maxHeight = `${content.scrollHeight}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
function updateIfExpanded() {
|
||||||
const fn = (e) => {
|
if (expanded) {
|
||||||
if (expanded) {
|
updateSize();
|
||||||
updateSize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content.addEventListener('DOMNodeInserted', fn);
|
onMount(() => {
|
||||||
content.addEventListener('DOMNodeRemoved', fn);
|
content.addEventListener('DOMNodeInserted', updateIfExpanded);
|
||||||
|
content.addEventListener('DOMNodeRemoved', updateIfExpanded);
|
||||||
|
|
||||||
|
if (defaultOpen) toggle();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -76,5 +85,6 @@
|
|||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
19
frontend/src/components/PremiumBadge.svelte
Normal file
19
frontend/src/components/PremiumBadge.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Badge>
|
||||||
|
<div class="inner">
|
||||||
|
<i class="fas fa-gem"></i>
|
||||||
|
<span>Premium</span>
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Badge from "./Badge.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,11 +1,12 @@
|
|||||||
<div class:col-1={col1} class:col-2={col2} class:col-3={col3} class:col-4={col4}>
|
<div class:col-1={col1} class:col-2={col2} class:col-3={col3} class:col-4={col4}>
|
||||||
<label for="input" class="form-label">{label}</label>
|
<label for="input" class="form-label">{label}</label>
|
||||||
<input id="input" class="form-checkbox" type=checkbox bind:checked={value} on:change>
|
<input id="input" class="form-checkbox" type=checkbox bind:checked={value} on:change {disabled}>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export let value;
|
export let value;
|
||||||
export let label;
|
export let label;
|
||||||
|
export let disabled = false;
|
||||||
|
|
||||||
export let col1 = false;
|
export let col1 = false;
|
||||||
export let col2 = false;
|
export let col2 = false;
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="row label">
|
<div class="row label">
|
||||||
<div class="parent">
|
<slot name="header"></slot>
|
||||||
<label class="form-label">{label}</label>
|
|
||||||
{#if badge !== undefined}
|
|
||||||
<Badge>{badge}</Badge>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row fields">
|
<div class="row fields">
|
||||||
@ -27,10 +22,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Badge from "../Badge.svelte";
|
|
||||||
|
|
||||||
export let label;
|
|
||||||
export let badge;
|
|
||||||
export let disabled = false; // note: bind:disabled isn't valid
|
export let disabled = false; // note: bind:disabled isn't valid
|
||||||
|
|
||||||
export let days = 0;
|
export let days = 0;
|
||||||
@ -88,6 +79,12 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.parent {
|
.parent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -1,66 +1,165 @@
|
|||||||
<Card footer="{false}" fill="{false}">
|
{#if data}
|
||||||
|
<Card footer="{false}" fill="{false}">
|
||||||
<span slot="title">
|
<span slot="title">
|
||||||
Settings
|
Settings
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div slot="body" class="body-wrapper">
|
<div slot="body" class="body-wrapper">
|
||||||
<form class="settings-form" on:submit|preventDefault={updateSettings}>
|
<form class="settings-form" on:submit|preventDefault={updateSettings}>
|
||||||
<div class="row">
|
<Collapsible defaultOpen>
|
||||||
<Input label="prefix (max len. 8)" placeholder="t!" col4=true bind:value={data.prefix}/>
|
<span slot="header">General</span>
|
||||||
<Number label="per user simultaneous ticket limit" col4=true min=1 max=10 bind:value={data.ticket_limit}/>
|
<div slot="content" class="col-1">
|
||||||
<Checkbox label="allow users to close tickets" col4=true bind:value={data.users_can_close}/>
|
<div class="row">
|
||||||
<Checkbox label="ticket close confirmation" col4=true bind:value={data.close_confirmation}/>
|
<Input label="prefix (max len. 8)" placeholder="t!" col4 bind:value={data.prefix}/>
|
||||||
</div>
|
<Number label="per user simultaneous ticket limit" min=1 max=10 bind:value={data.ticket_limit}/>
|
||||||
<div class="row">
|
<Dropdown label="Language" bind:value={data.language}>
|
||||||
<Textarea label="welcome message" placeholder="Thanks for opening a ticket!" col1=true
|
<option value=null selected="selected">Server Default</option>
|
||||||
bind:value={data.welcome_message}/>
|
{#if data.languages}
|
||||||
</div>
|
{#each data.languages as language}
|
||||||
<div class="row">
|
<option value={language}>{data.language_names[language]}</option>
|
||||||
<ChannelDropdown label="Archive Channel" col3=true channels={channels} withNull={true} bind:value={data.archive_channel}/>
|
{/each}
|
||||||
<CategoryDropdown label="Channel Category" col3=true channels={channels} bind:value={data.category}/>
|
{/if}
|
||||||
<Dropdown label="Overflow Category" col3=true bind:value={data.overflow_category_id}>
|
</Dropdown>
|
||||||
<option value=-1>Disabled</option>
|
<Checkbox label="allow users to close tickets" bind:value={data.users_can_close}/>
|
||||||
<option value=-2>Uncategorised (Appears at top of channel list)</option>
|
<Checkbox label="ticket close confirmation" bind:value={data.close_confirmation}/>
|
||||||
{#each channels as channel}
|
<Checkbox label="Enable User Feedback" bind:value={data.feedback_enabled}/>
|
||||||
{#if channel.type === 4}
|
</div>
|
||||||
<option value={channel.id}>
|
</div>
|
||||||
{channel.name}
|
</Collapsible>
|
||||||
</option>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<NamingScheme col4=true bind:value={data.naming_scheme}/>
|
|
||||||
<Checkbox label="Enable User Feedback" col4=true bind:value={data.feedback_enabled}/>
|
|
||||||
<Checkbox label="Hide Claim Button" col4=true bind:value={data.hide_claim_button}/>
|
|
||||||
<Checkbox label="Disable /open Command" col4=true bind:value={data.disable_open_command}/>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<Checkbox label="Store Ticket Transcripts" col4=true bind:value={data.store_transcripts}/>
|
|
||||||
</div>
|
|
||||||
<div class="from-message-settings">
|
|
||||||
<h3>Start Ticket From Message Settings</h3>
|
|
||||||
<div class="row">
|
|
||||||
<Dropdown col3={true} label="Required Permission Level" bind:value={data.context_menu_permission_level}>
|
|
||||||
<option value="0">Everyone</option>
|
|
||||||
<option value="1">Support Representative</option>
|
|
||||||
<option value="2">Administrator</option>
|
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
<Checkbox label="Add Message Sender To Ticket" col3={true} bind:value={data.context_menu_add_sender}/>
|
<Collapsible defaultOpen>
|
||||||
<SimplePanelDropdown label="Use Settings From Panel" col3={true} allowNone={true} bind:panels
|
<span slot="header">Tickets</span>
|
||||||
bind:value={data.context_menu_panel}/>
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<ChannelDropdown label="Archive Channel" col3=true channels={channels} withNull={true}
|
||||||
|
bind:value={data.archive_channel}/>
|
||||||
|
<Dropdown label="Overflow Category" col3=true bind:value={data.overflow_category_id}>
|
||||||
|
<option value=-1>Disabled</option>
|
||||||
|
<option value=-2>Uncategorised (Appears at top of channel list)</option>
|
||||||
|
{#each channels as channel}
|
||||||
|
{#if channel.type === 4}
|
||||||
|
<option value={channel.id}>
|
||||||
|
{channel.name}
|
||||||
|
</option>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</Dropdown>
|
||||||
|
<Checkbox label="Store Ticket Transcripts" bind:value={data.store_transcripts}/>
|
||||||
|
<Checkbox label="Hide Claim Button" bind:value={data.hide_claim_button}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<Collapsible>
|
||||||
|
<span slot="header">/Open Command</span>
|
||||||
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<Checkbox label="Disable /open Command" bind:value={data.disable_open_command}/>
|
||||||
|
<CategoryDropdown label="Channel Category" col3 channels={channels} bind:value={data.category}/>
|
||||||
|
<NamingScheme bind:value={data.naming_scheme}/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1-flex">
|
||||||
|
<Textarea label="welcome message" placeholder="Thanks for opening a ticket!" col1
|
||||||
|
bind:value={data.welcome_message}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<Collapsible>
|
||||||
|
<span slot="header">Context Menu (Start Ticket Dropdown)</span>
|
||||||
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<Dropdown col3 label="Required Permission Level" bind:value={data.context_menu_permission_level}>
|
||||||
|
<option value="0">Everyone</option>
|
||||||
|
<option value="1">Support Representative</option>
|
||||||
|
<option value="2">Administrator</option>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
<Checkbox label="Add Message Sender To Ticket" bind:value={data.context_menu_add_sender}/>
|
||||||
|
<SimplePanelDropdown label="Use Settings From Panel" col3 allowNone={true} bind:panels
|
||||||
|
bind:value={data.context_menu_panel}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<Collapsible>
|
||||||
|
<span slot="header">Claiming</span>
|
||||||
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<Checkbox label="SUPPORT REPS CAN VIEW CLAIMED TICKETS" bind:value={data.claim_settings.support_can_view}
|
||||||
|
on:change={validateView}/>
|
||||||
|
<Checkbox label="SUPPORT REPS CAN TYPE IN CLAIMED TICKETS" bind:value={data.claim_settings.support_can_type}
|
||||||
|
on:change={validateType}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<Collapsible>
|
||||||
|
<span slot="header">Auto Close</span>
|
||||||
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<Checkbox label="Enabled" bind:value={data.auto_close.enabled}/>
|
||||||
|
<Checkbox label="Close On User Leave" disabled={!data.auto_close.enabled} bind:value={data.auto_close.on_user_leave}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="justify-content: space-between">
|
||||||
|
<div class="col-2" style="flex-direction: row">
|
||||||
|
<Duration disabled={!isPremium || !data.auto_close.enabled} bind:days={sinceOpenDays} bind:hours={sinceOpenHours}
|
||||||
|
bind:minutes={sinceOpenMinutes}>
|
||||||
|
<div slot="header" class="header">
|
||||||
|
<label class="form-label" style="margin-bottom: unset">Since Open With No Response</label>
|
||||||
|
<PremiumBadge/>
|
||||||
|
</div>
|
||||||
|
</Duration>
|
||||||
|
</div>
|
||||||
|
<div class="col-2" style="flex-direction: row">
|
||||||
|
<Duration disabled={!isPremium || !data.auto_close.enabled} bind:days={sinceLastDays} bind:hours={sinceLastHours}
|
||||||
|
bind:minutes={sinceLastMinutes}>
|
||||||
|
<div slot="header" class="header">
|
||||||
|
<label class="form-label" style="margin-bottom: unset">Since Last Message</label>
|
||||||
|
<PremiumBadge/>
|
||||||
|
</div>
|
||||||
|
</Duration>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<Collapsible>
|
||||||
|
<div slot="header" class="header">
|
||||||
|
<span>Colour Scheme</span>
|
||||||
|
<PremiumBadge/>
|
||||||
|
</div>
|
||||||
|
<div slot="content" class="col-1">
|
||||||
|
<div class="row">
|
||||||
|
<Colour col4 label="Success" bind:value={data.colours["0"]} disabled={!isPremium}/>
|
||||||
|
<Colour col4 label="Failure" bind:value={data.colours["1"]} disabled={!isPremium}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
<Button icon="fas fa-paper-plane" fullWidth=true>Submit</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-1">
|
</Card>
|
||||||
<Button icon="fas fa-paper-plane" fullWidth=true>Submit</Button>
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
<svelte:head>
|
||||||
</form>
|
<style>
|
||||||
</div>
|
body {
|
||||||
</Card>
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ChannelDropdown from "../ChannelDropdown.svelte";
|
import ChannelDropdown from "../ChannelDropdown.svelte";
|
||||||
@ -79,6 +178,11 @@
|
|||||||
import NamingScheme from "../NamingScheme.svelte";
|
import NamingScheme from "../NamingScheme.svelte";
|
||||||
import Dropdown from "../form/Dropdown.svelte";
|
import Dropdown from "../form/Dropdown.svelte";
|
||||||
import SimplePanelDropdown from "../SimplePanelDropdown.svelte";
|
import SimplePanelDropdown from "../SimplePanelDropdown.svelte";
|
||||||
|
import Collapsible from "../Collapsible.svelte";
|
||||||
|
import Duration from "../form/Duration.svelte";
|
||||||
|
import Colour from "../form/Colour.svelte";
|
||||||
|
import PremiumBadge from "../PremiumBadge.svelte";
|
||||||
|
import {toDays, toHours, toMinutes} from "../../js/timeutil";
|
||||||
|
|
||||||
export let guildId;
|
export let guildId;
|
||||||
|
|
||||||
@ -86,6 +190,24 @@
|
|||||||
|
|
||||||
let channels = [];
|
let channels = [];
|
||||||
let panels = [];
|
let panels = [];
|
||||||
|
let isPremium = false;
|
||||||
|
|
||||||
|
let data;
|
||||||
|
|
||||||
|
let sinceOpenDays = 0, sinceOpenHours = 0, sinceOpenMinutes = 0;
|
||||||
|
let sinceLastDays = 0, sinceLastHours = 0, sinceLastMinutes = 0;
|
||||||
|
|
||||||
|
function validateView() {
|
||||||
|
if (!data.support_can_view && data.support_can_type) {
|
||||||
|
data.support_can_type = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateType() {
|
||||||
|
if (!data.support_can_view && data.support_can_type) {
|
||||||
|
data.support_can_view = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadPanels() {
|
async function loadPanels() {
|
||||||
const res = await axios.get(`${API_URL}/api/${guildId}/panels`);
|
const res = await axios.get(`${API_URL}/api/${guildId}/panels`);
|
||||||
@ -107,11 +229,15 @@
|
|||||||
channels = res.data;
|
channels = res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {
|
async function loadPremium() {
|
||||||
ticket_limit: 5,
|
const res = await axios.get(`${API_URL}/api/${guildId}/premium`);
|
||||||
users_can_close: true,
|
if (res.status !== 200) {
|
||||||
close_confirmation: true,
|
notifyError(res.data.error);
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPremium = res.data.premium;
|
||||||
|
}
|
||||||
|
|
||||||
async function updateSettings() {
|
async function updateSettings() {
|
||||||
// Svelte hack - I can't even remember what this does
|
// Svelte hack - I can't even remember what this does
|
||||||
@ -135,6 +261,10 @@
|
|||||||
mapped.overflow_enabled = true;
|
mapped.overflow_enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalise autoclose
|
||||||
|
data.auto_close.since_open_with_no_response = sinceOpenDays * 86400 + sinceOpenHours * 3600 + sinceOpenMinutes * 60;
|
||||||
|
data.auto_close.since_last_message = sinceLastDays * 86400 + sinceLastHours * 3600 + sinceLastMinutes * 60;
|
||||||
|
|
||||||
const res = await axios.post(`${API_URL}/api/${guildId}/settings`, mapped);
|
const res = await axios.post(`${API_URL}/api/${guildId}/settings`, mapped);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
if (showValidations(res.data)) {
|
if (showValidations(res.data)) {
|
||||||
@ -152,8 +282,8 @@
|
|||||||
let success = true;
|
let success = true;
|
||||||
|
|
||||||
if (data.error !== null) {
|
if (data.error !== null) {
|
||||||
success = false;
|
success = false;
|
||||||
notify("Warning", data.error);
|
notify("Warning", data.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.prefix) {
|
if (!data.prefix) {
|
||||||
@ -214,22 +344,40 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.overflow_enabled === false) {
|
if (data.overflow_enabled === false) {
|
||||||
data.overflow_category_id = "-1";
|
data.overflow_category_id = "-1";
|
||||||
} else if (data.overflow_enabled === true) {
|
} else if (data.overflow_enabled === true) {
|
||||||
if (data.overflow_category_id === null) {
|
if (data.overflow_category_id === null) {
|
||||||
data.overflow_category_id = "-2";
|
data.overflow_category_id = "-2";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!channels.some((c) => c.id === data.overflow_category_id)) {
|
if (!channels.some((c) => c.id === data.overflow_category_id)) {
|
||||||
data.overflow_category_id = "-2";
|
data.overflow_category_id = "-2";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.language === null) {
|
||||||
|
data.language = "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto close overrides
|
||||||
|
if (data.auto_close.since_open_with_no_response) {
|
||||||
|
sinceOpenDays = toDays(data.auto_close.since_open_with_no_response);
|
||||||
|
sinceOpenHours = toHours(data.auto_close.since_open_with_no_response);
|
||||||
|
sinceOpenMinutes = toMinutes(data.auto_close.since_open_with_no_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.auto_close.since_last_message) {
|
||||||
|
sinceLastDays = toDays(data.auto_close.since_last_message);
|
||||||
|
sinceLastHours = toHours(data.auto_close.since_last_message);
|
||||||
|
sinceLastMinutes = toMinutes(data.auto_close.since_last_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withLoadingScreen(async () => {
|
withLoadingScreen(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadPanels(),
|
loadPanels(),
|
||||||
loadChannels()
|
loadChannels(),
|
||||||
|
loadPremium()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await loadData(); // Depends on channels
|
await loadData(); // Depends on channels
|
||||||
@ -245,7 +393,9 @@
|
|||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -258,10 +408,11 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.from-message-settings {
|
.col-1-flex {
|
||||||
border-top: 1px solid rgba(0, 0, 0, .25);
|
display: flex;
|
||||||
margin-top: 25px;
|
flex-direction: column;
|
||||||
padding-top: 10px;
|
align-items: flex-start;
|
||||||
|
flex: 0 0 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 950px) {
|
@media only screen and (max-width: 950px) {
|
||||||
@ -306,4 +457,11 @@
|
|||||||
width: 23%;
|
width: 23%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -30,14 +30,11 @@
|
|||||||
<NavElement icon="fas fa-ticket-alt" link="/manage/{guildId}/tickets" on:click={closeDropdown}>Tickets</NavElement>
|
<NavElement icon="fas fa-ticket-alt" link="/manage/{guildId}/tickets" on:click={closeDropdown}>Tickets</NavElement>
|
||||||
<NavElement icon="fas fa-ban" link="/manage/{guildId}/blacklist" on:click={closeDropdown}>Blacklist</NavElement>
|
<NavElement icon="fas fa-ban" link="/manage/{guildId}/blacklist" on:click={closeDropdown}>Blacklist</NavElement>
|
||||||
<NavElement icon="fas fa-tags" link="/manage/{guildId}/tags" on:click={closeDropdown}>Tags</NavElement>
|
<NavElement icon="fas fa-tags" link="/manage/{guildId}/tags" on:click={closeDropdown}>Tags</NavElement>
|
||||||
|
|
||||||
{#if isAdmin}
|
|
||||||
<NavElement icon="fas fa-paint-brush" link="/manage/{guildId}/appearance" on:click={closeDropdown}>Appearance</NavElement>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="nav-section" class:dropdown={$dropdown}>
|
<div class="nav-section" class:dropdown={$dropdown}>
|
||||||
|
<NavElement icon="fas fa-book" link="https://docs.ticketsbot.net">Documentation</NavElement>
|
||||||
<NavElement icon="fas fa-server" link="/#">Servers</NavElement>
|
<NavElement icon="fas fa-server" link="/#">Servers</NavElement>
|
||||||
<NavElement icon="fas fa-sign-out-alt" link="/logout">Logout</NavElement>
|
<NavElement icon="fas fa-sign-out-alt" link="/logout">Logout</NavElement>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,23 +3,11 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<SettingsCard {guildId}/>
|
<SettingsCard {guildId}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
|
||||||
<AutoCloseCard {guildId}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right-col">
|
|
||||||
<div class="card">
|
|
||||||
<ClaimsCard {guildId}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SettingsCard from "../components/manage/SettingsCard.svelte";
|
import SettingsCard from "../components/manage/SettingsCard.svelte";
|
||||||
import AutoCloseCard from "../components/manage/AutoCloseCard.svelte";
|
|
||||||
import ClaimsCard from "../components/manage/ClaimsCard.svelte";
|
|
||||||
import {onMount} from "svelte";
|
|
||||||
import {dropdown} from "../js/stores";
|
|
||||||
|
|
||||||
export let currentRoute;
|
export let currentRoute;
|
||||||
let guildId = currentRoute.namedParams.id
|
let guildId = currentRoute.namedParams.id
|
||||||
@ -39,7 +27,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 66.6%;
|
width: 100%;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
|||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
|
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
|
||||||
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42
|
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42
|
||||||
github.com/TicketsBot/database v0.0.0-20220723212053-ab122ba82749
|
github.com/TicketsBot/database v0.0.0-20220725214217-fe953c05126d
|
||||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
|
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
|
||||||
github.com/TicketsBot/worker v0.0.0-20220710121124-cd5ec72739f9
|
github.com/TicketsBot/worker v0.0.0-20220710121124-cd5ec72739f9
|
||||||
github.com/apex/log v1.1.2
|
github.com/apex/log v1.1.2
|
||||||
|
2
go.sum
2
go.sum
@ -7,6 +7,8 @@ github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42 h1:3/qnbrEfL8gqS
|
|||||||
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42/go.mod h1:WxHh6bY7KhIqdayeOp5f0Zj2NNi/7QqCQfMEqHnpdAM=
|
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42/go.mod h1:WxHh6bY7KhIqdayeOp5f0Zj2NNi/7QqCQfMEqHnpdAM=
|
||||||
github.com/TicketsBot/database v0.0.0-20220723212053-ab122ba82749 h1:U/TnoBH3AyeV8uuQK/g69NfdNzGYGnjMD5KryJ+93Ok=
|
github.com/TicketsBot/database v0.0.0-20220723212053-ab122ba82749 h1:U/TnoBH3AyeV8uuQK/g69NfdNzGYGnjMD5KryJ+93Ok=
|
||||||
github.com/TicketsBot/database v0.0.0-20220723212053-ab122ba82749/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
|
github.com/TicketsBot/database v0.0.0-20220723212053-ab122ba82749/go.mod h1:F57cywrZsnper1cy56Bx0c/HEsxQBLHz3Pl98WXblWw=
|
||||||
|
github.com/TicketsBot/database v0.0.0-20220725214217-fe953c05126d h1:Xjlg6CHM+rXl5kWJevZspK5SxcGtaY3xE1q64/MDkx8=
|
||||||
|
github.com/TicketsBot/database v0.0.0-20220725214217-fe953c05126d/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 h1:OqGjFH6mbE6gd+NqI2ARJdtH3UUvhiAkD0r0fhGJK2s=
|
||||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c/go.mod h1:jgi2OXQKsd5nUnTIRkwvPmeuD/i7OhN68LKMssuQY1c=
|
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=
|
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user