Add ACLs
This commit is contained in:
parent
263b5a3252
commit
c600b91109
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/TicketsBot/GoPanel/app/http/validation"
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
@ -8,11 +9,11 @@ import (
|
||||
"github.com/TicketsBot/GoPanel/rpc"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/GoPanel/utils/types"
|
||||
"github.com/TicketsBot/common/collections"
|
||||
"github.com/TicketsBot/common/premium"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/rxdn/gdl/objects/guild/emoji"
|
||||
"github.com/rxdn/gdl/objects/interaction/component"
|
||||
"github.com/rxdn/gdl/rest/request"
|
||||
@ -22,25 +23,26 @@ import (
|
||||
const freePanelLimit = 3
|
||||
|
||||
type panelBody struct {
|
||||
ChannelId uint64 `json:"channel_id,string"`
|
||||
MessageId uint64 `json:"message_id,string"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Colour uint32 `json:"colour"`
|
||||
CategoryId uint64 `json:"category_id,string"`
|
||||
Emoji types.Emoji `json:"emote"`
|
||||
WelcomeMessage *types.CustomEmbed `json:"welcome_message" validate:"omitempty,dive"`
|
||||
Mentions []string `json:"mentions"`
|
||||
WithDefaultTeam bool `json:"default_team"`
|
||||
Teams []int `json:"teams"`
|
||||
ImageUrl *string `json:"image_url,omitempty"`
|
||||
ThumbnailUrl *string `json:"thumbnail_url,omitempty"`
|
||||
ButtonStyle component.ButtonStyle `json:"button_style,string"`
|
||||
ButtonLabel string `json:"button_label"`
|
||||
FormId *int `json:"form_id"`
|
||||
NamingScheme *string `json:"naming_scheme"`
|
||||
Disabled bool `json:"disabled"`
|
||||
ExitSurveyFormId *int `json:"exit_survey_form_id"`
|
||||
ChannelId uint64 `json:"channel_id,string"`
|
||||
MessageId uint64 `json:"message_id,string"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Colour uint32 `json:"colour"`
|
||||
CategoryId uint64 `json:"category_id,string"`
|
||||
Emoji types.Emoji `json:"emote"`
|
||||
WelcomeMessage *types.CustomEmbed `json:"welcome_message" validate:"omitempty,dive"`
|
||||
Mentions []string `json:"mentions"`
|
||||
WithDefaultTeam bool `json:"default_team"`
|
||||
Teams []int `json:"teams"`
|
||||
ImageUrl *string `json:"image_url,omitempty"`
|
||||
ThumbnailUrl *string `json:"thumbnail_url,omitempty"`
|
||||
ButtonStyle component.ButtonStyle `json:"button_style,string"`
|
||||
ButtonLabel string `json:"button_label"`
|
||||
FormId *int `json:"form_id"`
|
||||
NamingScheme *string `json:"naming_scheme"`
|
||||
Disabled bool `json:"disabled"`
|
||||
ExitSurveyFormId *int `json:"exit_survey_form_id"`
|
||||
AccessControlList []database.PanelAccessControlRule `json:"access_control_list"`
|
||||
}
|
||||
|
||||
func (p *panelBody) IntoPanelMessageData(customId string, isPremium bool) panelMessageData {
|
||||
@ -109,6 +111,12 @@ func CreatePanel(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := botContext.GetGuildRoles(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Do custom validation
|
||||
validationContext := PanelValidationContext{
|
||||
Data: data,
|
||||
@ -116,6 +124,7 @@ func CreatePanel(ctx *gin.Context) {
|
||||
IsPremium: premiumTier > premium.None,
|
||||
BotContext: botContext,
|
||||
Channels: channels,
|
||||
Roles: roles,
|
||||
}
|
||||
|
||||
if err := ValidatePanelBody(validationContext); err != nil {
|
||||
@ -215,30 +224,19 @@ func CreatePanel(ctx *gin.Context) {
|
||||
ExitSurveyFormId: data.ExitSurveyFormId,
|
||||
}
|
||||
|
||||
panelId, err := dbclient.Client.Panel.Create(panel)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
createOptions := panelCreateOptions{
|
||||
TeamIds: data.Teams, // Already validated
|
||||
AccessControlRules: data.AccessControlList, // Already validated
|
||||
}
|
||||
|
||||
// insert role mention data
|
||||
// string is role ID or "user" to mention the ticket opener
|
||||
validRoles, err := getRoleHashSet(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
validRoles := utils.ToSet(utils.Map(roles, utils.RoleToId))
|
||||
|
||||
var roleMentions []uint64
|
||||
for _, mention := range data.Mentions {
|
||||
if mention == "user" {
|
||||
if err = dbclient.Client.PanelUserMention.Set(panelId, true); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
createOptions.ShouldMentionUser = true
|
||||
} else {
|
||||
roleId, err := strconv.ParseUint(mention, 10, 64)
|
||||
if err != nil {
|
||||
@ -247,18 +245,13 @@ func CreatePanel(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if validRoles.Contains(roleId) {
|
||||
roleMentions = append(roleMentions, roleId)
|
||||
createOptions.RoleMentions = append(roleMentions, roleId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelRoleMentions.Replace(panelId, roleMentions); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Already validated, we are safe to insert
|
||||
if err := dbclient.Client.PanelTeams.Replace(panelId, data.Teams); err != nil {
|
||||
panelId, err := storePanel(ctx, panel, createOptions)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
@ -269,27 +262,52 @@ func CreatePanel(ctx *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// DB functions
|
||||
|
||||
type panelCreateOptions struct {
|
||||
ShouldMentionUser bool
|
||||
RoleMentions []uint64
|
||||
TeamIds []int
|
||||
AccessControlRules []database.PanelAccessControlRule
|
||||
}
|
||||
|
||||
func storePanel(ctx context.Context, panel database.Panel, options panelCreateOptions) (int, error) {
|
||||
var panelId int
|
||||
err := dbclient.Client.Panel.BeginFunc(ctx, func(tx pgx.Tx) error {
|
||||
var err error
|
||||
panelId, err = dbclient.Client.Panel.CreateWithTx(tx, panel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelUserMention.SetWithTx(tx, panelId, options.ShouldMentionUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelRoleMentions.ReplaceWithTx(tx, panelId, options.RoleMentions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Already validated, we are safe to insert
|
||||
if err := dbclient.Client.PanelTeams.ReplaceWithTx(tx, panelId, options.TeamIds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelAccessControlRules.ReplaceWithTx(ctx, tx, panelId, options.AccessControlRules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return panelId, nil
|
||||
}
|
||||
|
||||
// Data must be validated before calling this function
|
||||
func (p *panelBody) getEmoji() *emoji.Emoji {
|
||||
return p.Emoji.IntoGdl()
|
||||
}
|
||||
|
||||
func getRoleHashSet(guildId uint64) (*collections.Set[uint64], error) {
|
||||
ctx, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles, err := ctx.GetGuildRoles(guildId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set := collections.NewSet[uint64]()
|
||||
|
||||
for _, role := range roles {
|
||||
set.Add(role.Id)
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
@ -14,12 +14,13 @@ import (
|
||||
func ListPanels(ctx *gin.Context) {
|
||||
type panelResponse struct {
|
||||
database.Panel
|
||||
WelcomeMessage *types.CustomEmbed `json:"welcome_message"`
|
||||
UseCustomEmoji bool `json:"use_custom_emoji"`
|
||||
Emoji types.Emoji `json:"emote"`
|
||||
Mentions []string `json:"mentions"`
|
||||
Teams []int `json:"teams"`
|
||||
UseServerDefaultNamingScheme bool `json:"use_server_default_naming_scheme"`
|
||||
WelcomeMessage *types.CustomEmbed `json:"welcome_message"`
|
||||
UseCustomEmoji bool `json:"use_custom_emoji"`
|
||||
Emoji types.Emoji `json:"emote"`
|
||||
Mentions []string `json:"mentions"`
|
||||
Teams []int `json:"teams"`
|
||||
UseServerDefaultNamingScheme bool `json:"use_server_default_naming_scheme"`
|
||||
AccessControlList []database.PanelAccessControlRule `json:"access_control_list"`
|
||||
}
|
||||
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
@ -30,6 +31,12 @@ func ListPanels(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
accessControlLists, err := dbclient.Client.PanelAccessControlRules.GetAllForGuild(ctx, guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
allFields, err := dbclient.Client.EmbedFields.GetAllFieldsForPanels(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
@ -85,6 +92,11 @@ func ListPanels(ctx *gin.Context) {
|
||||
welcomeMessage = types.NewCustomEmbed(p.WelcomeMessage, fields)
|
||||
}
|
||||
|
||||
accessControlList := accessControlLists[p.PanelId]
|
||||
if accessControlList == nil {
|
||||
accessControlList = make([]database.PanelAccessControlRule, 0)
|
||||
}
|
||||
|
||||
wrapped[i] = panelResponse{
|
||||
Panel: p.Panel,
|
||||
WelcomeMessage: welcomeMessage,
|
||||
@ -93,6 +105,7 @@ func ListPanels(ctx *gin.Context) {
|
||||
Mentions: mentions,
|
||||
Teams: teamIds,
|
||||
UseServerDefaultNamingScheme: p.NamingScheme == nil,
|
||||
AccessControlList: accessControlList,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/rxdn/gdl/objects/interaction/component"
|
||||
"github.com/rxdn/gdl/rest"
|
||||
"github.com/rxdn/gdl/rest/request"
|
||||
@ -71,6 +72,12 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := botContext.GetGuildRoles(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Do custom validation
|
||||
validationContext := PanelValidationContext{
|
||||
Data: data,
|
||||
@ -78,6 +85,7 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
IsPremium: premiumTier > premium.None,
|
||||
BotContext: botContext,
|
||||
Channels: channels,
|
||||
Roles: roles,
|
||||
}
|
||||
|
||||
if err := ValidatePanelBody(validationContext); err != nil {
|
||||
@ -213,17 +221,8 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
ExitSurveyFormId: data.ExitSurveyFormId,
|
||||
}
|
||||
|
||||
if err = dbclient.Client.Panel.Update(panel); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// insert mention data
|
||||
validRoles, err := getRoleHashSet(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
validRoles := utils.ToSet(utils.Map(roles, utils.RoleToId))
|
||||
|
||||
// string is role ID or "user" to mention the ticket opener
|
||||
var shouldMentionUser bool
|
||||
@ -244,22 +243,37 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelUserMention.Set(panel.PanelId, shouldMentionUser); err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
err = dbclient.Client.Panel.BeginFunc(ctx, func(tx pgx.Tx) error {
|
||||
if err := dbclient.Client.Panel.UpdateWithTx(tx, panel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelRoleMentions.Replace(panel.PanelId, roleMentions); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// We are safe to insert, team IDs already validated
|
||||
if err := dbclient.Client.PanelTeams.Replace(panel.PanelId, data.Teams); err != nil {
|
||||
if err := dbclient.Client.PanelUserMention.SetWithTx(tx, panel.PanelId, shouldMentionUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelRoleMentions.ReplaceWithTx(tx, panel.PanelId, roleMentions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We are safe to insert, team IDs already validated
|
||||
if err := dbclient.Client.PanelTeams.ReplaceWithTx(tx, panel.PanelId, data.Teams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbclient.Client.PanelAccessControlRules.ReplaceWithTx(ctx, tx, panel.PanelId, data.AccessControlList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// This doesn't need to be done in a transaction
|
||||
// Update multi panels
|
||||
|
||||
// check if this will break a multi-panel;
|
||||
|
@ -9,14 +9,24 @@ import (
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/rxdn/gdl/objects/channel"
|
||||
"github.com/rxdn/gdl/objects/guild"
|
||||
"github.com/rxdn/gdl/objects/interaction/component"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ApplyPanelDefaults(data *panelBody) []defaults.DefaultApplicator {
|
||||
func ApplyPanelDefaults(data *panelBody) {
|
||||
for _, applicator := range DefaultApplicators(data) {
|
||||
if applicator.ShouldApply() {
|
||||
applicator.Apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultApplicators(data *panelBody) []defaults.DefaultApplicator {
|
||||
return []defaults.DefaultApplicator{
|
||||
defaults.NewDefaultApplicator(defaults.EmptyStringCheck, &data.Title, "Open a ticket!"),
|
||||
defaults.NewDefaultApplicator(defaults.EmptyStringCheck, &data.Content, "By clicking the button, a ticket will be opened for you."),
|
||||
@ -34,6 +44,7 @@ type PanelValidationContext struct {
|
||||
IsPremium bool
|
||||
BotContext botcontext.BotContext
|
||||
Channels []channel.Channel
|
||||
Roles []guild.Role
|
||||
}
|
||||
|
||||
func ValidatePanelBody(validationContext PanelValidationContext) error {
|
||||
@ -59,6 +70,7 @@ func panelValidators() []validation.Validator[PanelValidationContext] {
|
||||
validateTeams,
|
||||
validateNamingScheme,
|
||||
validateWelcomeMessage,
|
||||
validateAccessControlList,
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,3 +294,44 @@ func validateWelcomeMessage(ctx PanelValidationContext) validation.ValidationFun
|
||||
return validation.NewInvalidInputError("Welcome message has no content")
|
||||
}
|
||||
}
|
||||
|
||||
func validateAccessControlList(ctx PanelValidationContext) validation.ValidationFunc {
|
||||
return func() error {
|
||||
acl := ctx.Data.AccessControlList
|
||||
|
||||
if len(acl) == 0 {
|
||||
return validation.NewInvalidInputError("Access control list is empty")
|
||||
}
|
||||
|
||||
if len(acl) > 10 {
|
||||
return validation.NewInvalidInputError("Access control list cannot have more than 10 roles")
|
||||
}
|
||||
|
||||
roles := utils.ToSet(utils.Map(ctx.Roles, utils.RoleToId))
|
||||
|
||||
if roles.Size() != len(ctx.Roles) {
|
||||
return validation.NewInvalidInputError("Duplicate roles in access control list")
|
||||
}
|
||||
|
||||
everyoneRoleFound := false
|
||||
for _, rule := range acl {
|
||||
if rule.RoleId == ctx.GuildId {
|
||||
everyoneRoleFound = true
|
||||
}
|
||||
|
||||
if rule.Action != database.AccessControlActionDeny && rule.Action != database.AccessControlActionAllow {
|
||||
return validation.NewInvalidInputErrorf("Invalid access control action \"%s\"", rule.Action)
|
||||
}
|
||||
|
||||
if !roles.Contains(rule.RoleId) {
|
||||
return validation.NewInvalidInputErrorf("Invalid role %d in access control list not found in the guild", rule.RoleId)
|
||||
}
|
||||
}
|
||||
|
||||
if !everyoneRoleFound {
|
||||
return validation.NewInvalidInputError("Access control list does not contain @everyone rule")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package validation
|
||||
|
||||
import "fmt"
|
||||
|
||||
type InvalidInputError struct {
|
||||
Message string
|
||||
}
|
||||
@ -11,3 +13,7 @@ func (e *InvalidInputError) Error() string {
|
||||
func NewInvalidInputError(message string) *InvalidInputError {
|
||||
return &InvalidInputError{Message: message}
|
||||
}
|
||||
|
||||
func NewInvalidInputErrorf(message string, args ...any) *InvalidInputError {
|
||||
return &InvalidInputError{Message: fmt.Sprintf(message, args...)}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
{loadOptions}
|
||||
{placeholder}
|
||||
{placeholderAlwaysShow}
|
||||
{disabled}
|
||||
multiple={isMulti}
|
||||
--background="#2e3136"
|
||||
--border="#2e3136"
|
||||
@ -46,6 +47,7 @@
|
||||
export let placeholderAlwaysShow = false;
|
||||
export let loadOptions;
|
||||
export let loadOptionsInterval;
|
||||
export let disabled = false;
|
||||
|
||||
export let optionIdentifier;
|
||||
export let nameMapper = (x) => x;
|
||||
|
@ -2,8 +2,8 @@
|
||||
<label class="form-label">{label}</label>
|
||||
{/if}
|
||||
|
||||
<WrappedSelect placeholder="Search..." optionIdentifier="id" items={roles}
|
||||
bind:selectedValue={value} nameMapper={labelMapper}/>
|
||||
<WrappedSelect {placeholder} optionIdentifier="id" items={roles} {disabled}
|
||||
bind:selectedValue={value} nameMapper={labelMapper} on:change />
|
||||
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
@ -11,8 +11,10 @@
|
||||
import WrappedSelect from "../WrappedSelect.svelte";
|
||||
|
||||
export let label;
|
||||
export let placeholder = "Search...";
|
||||
export let roles = [];
|
||||
export let guildId;
|
||||
export let disabled = false;
|
||||
|
||||
export let value;
|
||||
|
||||
|
174
frontend/src/components/manage/AccessControlList.svelte
Normal file
174
frontend/src/components/manage/AccessControlList.svelte
Normal file
@ -0,0 +1,174 @@
|
||||
<div class="super">
|
||||
<RoleSelect {guildId} placeholder="Add another role..."
|
||||
roles={roles.filter((r) => !acl.find((s) => s.role_id === r.id))} disabled={acl.length >= maxAclSize}
|
||||
on:change={(e) => addToACL(e.detail)} bind:value={roleSelectorValue}/>
|
||||
|
||||
<div class="container">
|
||||
{#each acl as subject, i}
|
||||
{@const role = roles.find(r => r.id === subject.role_id)}
|
||||
<div class="subject">
|
||||
<div class="inner-left">
|
||||
<div class="row" style="gap: 10px">
|
||||
<div class="arrow-container">
|
||||
<i class="fa-solid fa-arrow-up position-arrow" class:disabled={i<=0} on:click={() => moveUp(i)}></i>
|
||||
<i class="fa-solid fa-arrow-down position-arrow" class:disabled={i>=acl.length-1}
|
||||
on:click={() => moveDown(i)}></i>
|
||||
</div>
|
||||
<span>{role.name}</span>
|
||||
</div>
|
||||
{#key rerender}
|
||||
<Toggle on="Allow" off="Deny"
|
||||
hideLabel
|
||||
toggledColor="#66bb6a"
|
||||
untoggledColor="#e84141"
|
||||
toggled={subject.action === "allow"}
|
||||
on:toggle={(e) => handleToggle(subject.role_id, e.detail)}/>
|
||||
{/key}
|
||||
</div>
|
||||
<div class="inner-right">
|
||||
{#if subject.role_id !== guildId}
|
||||
<div class="delete-button">
|
||||
<i class="fas fa-x" on:click={() => removeFromACL(subject)}></i>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import Toggle from "svelte-toggle";
|
||||
import RoleSelect from "../form/RoleSelect.svelte";
|
||||
|
||||
export let guildId;
|
||||
export let roles;
|
||||
export let maxAclSize = 10;
|
||||
export let acl = [
|
||||
{
|
||||
role_id: guildId,
|
||||
action: "allow"
|
||||
}
|
||||
];
|
||||
|
||||
let rerender = 0;
|
||||
let roleSelectorValue;
|
||||
|
||||
function handleToggle(roleId, enabled) {
|
||||
const subject = acl.find(s => s.role_id === roleId);
|
||||
subject.action = enabled ? "allow" : "deny";
|
||||
}
|
||||
|
||||
function addToACL(role) {
|
||||
acl = [
|
||||
{
|
||||
role_id: role.id,
|
||||
action: "allow"
|
||||
},
|
||||
...acl
|
||||
];
|
||||
|
||||
roleSelectorValue = null;
|
||||
|
||||
rerender++;
|
||||
}
|
||||
|
||||
function removeFromACL(subject) {
|
||||
if (subject.role_id === guildId) return;
|
||||
acl = acl.filter(s => s.role_id !== subject.role_id);
|
||||
|
||||
rerender++;
|
||||
}
|
||||
|
||||
function moveUp(index) {
|
||||
if (index <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmp = acl[index];
|
||||
acl[index] = acl[index - 1];
|
||||
acl[index - 1] = tmp;
|
||||
|
||||
rerender++;
|
||||
}
|
||||
|
||||
function moveDown(index) {
|
||||
if (index >= acl.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmp = acl[index];
|
||||
acl[index] = acl[index + 1];
|
||||
acl[index + 1] = tmp
|
||||
|
||||
rerender++;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.super {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
.subject {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding: 5px 20px;
|
||||
border-radius: 5px;
|
||||
background-color: #2e3136;
|
||||
}
|
||||
|
||||
.subject > .inner-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
gap: 20%;
|
||||
}
|
||||
|
||||
.subject > .inner-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
cursor: pointer;
|
||||
transform: scale(1.33333333333, 1);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.arrow-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.position-arrow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.position-arrow.disabled {
|
||||
cursor: unset !important;
|
||||
color: #777;
|
||||
}
|
||||
</style>
|
@ -154,28 +154,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible>
|
||||
<span slot="header">Access Control</span>
|
||||
<div slot="content" class="col-1">
|
||||
<div class="row">
|
||||
<p>Control who can open tickets with from this panel. Rules are evaluated from <em>top to bottom</em>,
|
||||
stopping after the first match.</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<AccessControlList {guildId} {roles} bind:acl={data.access_control_list} />
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
import Input from "../form/Input.svelte";
|
||||
import Textarea from "../form/Textarea.svelte";
|
||||
import Colour from "../form/Colour.svelte";
|
||||
import Button from "../Button.svelte";
|
||||
import ChannelDropdown from "../ChannelDropdown.svelte";
|
||||
import EmbedBuilder from "../EmbedBuilder.svelte";
|
||||
|
||||
import {createEventDispatcher, onMount} from 'svelte';
|
||||
import {colourToInt, intToColour} from "../../js/util";
|
||||
import CategoryDropdown from "../CategoryDropdown.svelte";
|
||||
import EmojiInput from "../form/EmojiInput.svelte";
|
||||
import EmojiItem from "../EmojiItem.svelte";
|
||||
import Select from 'svelte-select';
|
||||
import Dropdown from "../form/Dropdown.svelte";
|
||||
import Toggle from "svelte-toggle";
|
||||
import Checkbox from "../form/Checkbox.svelte";
|
||||
import Collapsible from "../Collapsible.svelte";
|
||||
import EmbedForm from "../EmbedForm.svelte";
|
||||
import WrappedSelect from "../WrappedSelect.svelte";
|
||||
import AccessControlList from "./AccessControlList.svelte";
|
||||
|
||||
export let guildId;
|
||||
export let seedDefault = true;
|
||||
@ -328,6 +338,12 @@
|
||||
footer: {},
|
||||
description: 'Thank you for contacting support.\nPlease describe your issue and wait for a response.'
|
||||
},
|
||||
access_control_list: [
|
||||
{
|
||||
role_id: guildId,
|
||||
action: "allow"
|
||||
}
|
||||
]
|
||||
};
|
||||
} else {
|
||||
applyOverrides();
|
||||
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
|
||||
github.com/TicketsBot/common v0.0.0-20230702161316-9b2fa80535aa
|
||||
github.com/TicketsBot/database v0.0.0-20230717191327-c63f2e0e2e27
|
||||
github.com/TicketsBot/database v0.0.0-20230821182620-0130c7c2c5ad
|
||||
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
|
||||
github.com/TicketsBot/worker v0.0.0-20230731124103-99c6834d9134
|
||||
github.com/apex/log v1.1.2
|
||||
|
2
go.sum
2
go.sum
@ -49,6 +49,8 @@ github.com/TicketsBot/common v0.0.0-20230702161316-9b2fa80535aa h1:6lMp2fzZvLpIq
|
||||
github.com/TicketsBot/common v0.0.0-20230702161316-9b2fa80535aa/go.mod h1:zN6qXS5AYkt4JTHtq7mHT3eBHomUWZoZ29dZ/CPMjHQ=
|
||||
github.com/TicketsBot/database v0.0.0-20230717191327-c63f2e0e2e27 h1:VbMTyqRPza4PRv/mRqJstLRD0tXTttPNeO2nLW/APfU=
|
||||
github.com/TicketsBot/database v0.0.0-20230717191327-c63f2e0e2e27/go.mod h1:gAtOoQKZfCkQ4AoNWQUSl51Fnlqk+odzD/hZ1e1sXyI=
|
||||
github.com/TicketsBot/database v0.0.0-20230821182620-0130c7c2c5ad h1:tg/KYNLExb8MJbrxxlVgSjIzSEOibduE3ZV1S0uz+7Y=
|
||||
github.com/TicketsBot/database v0.0.0-20230821182620-0130c7c2c5ad/go.mod h1:gAtOoQKZfCkQ4AoNWQUSl51Fnlqk+odzD/hZ1e1sXyI=
|
||||
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=
|
||||
|
2
locale
2
locale
@ -1 +1 @@
|
||||
Subproject commit e21b73209a122b7feb5c8672c34b73d0a7f6ab03
|
||||
Subproject commit b6540fa03b4f32ce126aa1d31830bbd4a0c2c6c7
|
@ -1,7 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/common/collections"
|
||||
"github.com/rxdn/gdl/objects/channel/message"
|
||||
"github.com/rxdn/gdl/objects/guild"
|
||||
)
|
||||
|
||||
func Contains[T comparable](slice []T, value T) bool {
|
||||
@ -21,3 +23,26 @@ func Reverse(slice []message.Message) []message.Message {
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func Map[T comparable, U any](slice []T, f func(T) U) []U {
|
||||
result := make([]U, len(slice))
|
||||
for i, elem := range slice {
|
||||
result[i] = f(elem)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func ToSet[T comparable](slice []T) *collections.Set[T] {
|
||||
set := collections.NewSet[T]()
|
||||
|
||||
for _, el := range slice {
|
||||
set.Add(el)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func RoleToId(role guild.Role) uint64 {
|
||||
return role.Id
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user