diff --git a/app/http/endpoints/api/blacklist/blacklist.go b/app/http/endpoints/api/blacklist/blacklist.go
index 5f36d69..ea9388e 100644
--- a/app/http/endpoints/api/blacklist/blacklist.go
+++ b/app/http/endpoints/api/blacklist/blacklist.go
@@ -2,20 +2,20 @@ package api
import (
"context"
- "fmt"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin"
+ "github.com/rxdn/gdl/objects/user"
"golang.org/x/sync/errgroup"
- "strconv"
- "sync"
)
type userData struct {
- Username string `json:"username"`
- Discriminator string `json:"discriminator"`
+ UserId uint64 `json:"id,string"`
+ Username string `json:"username"`
+ Discriminator user.Discriminator `json:"discriminator"`
}
+// TODO: Paginate
func GetBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
@@ -23,29 +23,31 @@ func GetBlacklistHandler(ctx *gin.Context) {
if err != nil {
ctx.JSON(500, gin.H{
"success": false,
- "error": err.Error(),
+ "error": err.Error(),
})
return
}
- data := make(map[string]userData)
- var lock sync.Mutex
+ data := make([]userData, len(blacklistedUsers))
group, _ := errgroup.WithContext(context.Background())
- for _, userId := range blacklistedUsers {
+ for i, userId := range blacklistedUsers {
+ i := i
+ userId := userId
+
+ // TODO: Mass lookup
group.Go(func() error {
- user, _ := cache.Instance.GetUser(userId)
-
- lock.Lock()
-
- // JS cant do big ints
- data[strconv.FormatUint(userId, 10)] = userData{
- Username: user.Username,
- Discriminator: fmt.Sprintf("%04d", user.Discriminator),
+ userData := userData{
+ UserId: userId,
}
- lock.Unlock()
+ user, ok := cache.Instance.GetUser(userId)
+ if ok {
+ userData.Username = user.Username
+ userData.Discriminator = user.Discriminator
+ }
+ data[i] = userData
return nil
})
}
diff --git a/app/http/endpoints/api/blacklist/blacklistadd.go b/app/http/endpoints/api/blacklist/blacklistadd.go
index bd3dd43..c0b978d 100644
--- a/app/http/endpoints/api/blacklist/blacklistadd.go
+++ b/app/http/endpoints/api/blacklist/blacklistadd.go
@@ -1,64 +1,37 @@
package api
import (
- "context"
- "errors"
- "fmt"
"github.com/TicketsBot/GoPanel/database"
- "github.com/TicketsBot/GoPanel/rpc/cache"
+ "github.com/TicketsBot/GoPanel/utils"
+ "github.com/TicketsBot/common/permission"
"github.com/gin-gonic/gin"
- "github.com/jackc/pgx/v4"
"strconv"
)
func AddBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
- var data userData
- if err := ctx.BindJSON(&data); err != nil {
- ctx.AbortWithStatusJSON(400, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
-
- parsedDiscrim, err := strconv.ParseInt(data.Discriminator, 10, 16)
+ id, err := strconv.ParseUint(ctx.Param("user"), 10, 64)
if err != nil {
- ctx.AbortWithStatusJSON(400, gin.H{
- "success": false,
- "error": err.Error(),
- })
+ ctx.JSON(400, utils.ErrorJson(err))
return
}
- var targetId uint64
- if err := cache.Instance.QueryRow(context.Background(), `select users.user_id from "users" where LOWER(users.data->>'Username')=LOWER($1) AND users.data->>'Discriminator'=$2;`, data.Username, strconv.FormatInt(parsedDiscrim, 10)).Scan(&targetId); err != nil {
- if errors.Is(err, pgx.ErrNoRows) {
- ctx.AbortWithStatusJSON(404, gin.H{
- "success": false,
- "error": "user not found",
- })
- } else {
- fmt.Println(err.Error())
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- }
+ permLevel, err := utils.GetPermissionLevel(guildId, id)
+ if err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
return
}
- // TODO: Don't blacklist staff or guild owner
- if err = database.Client.Blacklist.Add(guildId, targetId); err == nil {
- ctx.JSON(200, gin.H{
- "success": true,
- "user_id": strconv.FormatUint(targetId, 10),
- })
- } else {
- ctx.JSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
+ if permLevel > permission.Everyone {
+ ctx.JSON(400, utils.ErrorStr("You cannot blacklist staff members!"))
+ return
}
+
+ if err = database.Client.Blacklist.Add(guildId, id); err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
+
+ ctx.JSON(200, utils.SuccessResponse)
}
diff --git a/app/http/endpoints/api/logs/logslist.go b/app/http/endpoints/api/logs/logslist.go
deleted file mode 100644
index bb945a5..0000000
--- a/app/http/endpoints/api/logs/logslist.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package api
-
-import (
- "context"
- "fmt"
- "github.com/TicketsBot/GoPanel/botcontext"
- dbclient "github.com/TicketsBot/GoPanel/database"
- "github.com/TicketsBot/GoPanel/rpc/cache"
- "github.com/TicketsBot/GoPanel/utils"
- "github.com/TicketsBot/database"
- "github.com/apex/log"
- "github.com/gin-gonic/gin"
- "github.com/rxdn/gdl/rest"
- "strconv"
-)
-
-const (
- pageLimit = 30
-)
-
-func GetLogs(ctx *gin.Context) {
- guildId := ctx.Keys["guildid"].(uint64)
-
- botContext, err := botcontext.ContextForGuild(guildId)
- if err != nil {
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
-
- before, err := strconv.Atoi(ctx.Query("before"))
- if before < 0 {
- before = 0
- }
-
- // Get ticket ID from URL
- var ticketId int
- if utils.IsInt(ctx.Query("ticketid")) {
- ticketId, _ = strconv.Atoi(ctx.Query("ticketid"))
- }
-
- var tickets []database.Ticket
-
- // Get tickets from DB
- if ticketId > 0 {
- ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId)
- if err != nil {
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
-
- if ticket.UserId != 0 && !ticket.Open {
- tickets = append(tickets, ticket)
- }
- } else {
- // make slice of user IDs to filter by
- filteredIds := make([]uint64, 0)
-
- // Add userid param to slice
- filteredUserId, _ := strconv.ParseUint(ctx.Query("userid"), 10, 64)
- if filteredUserId != 0 {
- filteredIds = append(filteredIds, filteredUserId)
- }
-
- // Get username from URL
- if username := ctx.Query("username"); username != "" {
- // username -> user id
- rows, err := cache.Instance.PgCache.Query(context.Background(), `select users.user_id from users where LOWER("data"->>'Username') LIKE LOWER($1) and exists(SELECT FROM members where members.guild_id=$2);`, fmt.Sprintf("%%%s%%", username), guildId)
- defer rows.Close()
- if err != nil {
- log.Error(err.Error())
- return
- }
-
- for rows.Next() {
- var filteredId uint64
- if err := rows.Scan(&filteredId); err != nil {
- continue
- }
-
- if filteredId != 0 {
- filteredIds = append(filteredIds, filteredId)
- }
- }
- }
-
- if ctx.Query("userid") != "" || ctx.Query("username") != "" {
- tickets, err = dbclient.Client.Tickets.GetMemberClosedTickets(guildId, filteredIds, pageLimit, before)
- } else {
- tickets, err = dbclient.Client.Tickets.GetGuildClosedTickets(guildId, pageLimit, before)
- }
-
- if err != nil {
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
- }
-
- // Select 30 logs + format them
- formattedLogs := make([]map[string]interface{}, 0)
- for _, ticket := range tickets {
- // get username
- user, found := cache.Instance.GetUser(ticket.UserId)
- if !found {
- user, err = rest.GetUser(botContext.Token, botContext.RateLimiter, ticket.UserId)
- if err != nil {
- log.Error(err.Error())
- }
- go cache.Instance.StoreUser(user)
- }
-
- formattedLogs = append(formattedLogs, map[string]interface{}{
- "ticketid": ticket.Id,
- "userid": strconv.FormatUint(ticket.UserId, 10),
- "username": user.Username,
- })
- }
-
- ctx.JSON(200, formattedLogs)
-}
diff --git a/app/http/endpoints/api/panel/multipanelcreate.go b/app/http/endpoints/api/panel/multipanelcreate.go
index c324ea7..f0e9d5f 100644
--- a/app/http/endpoints/api/panel/multipanelcreate.go
+++ b/app/http/endpoints/api/panel/multipanelcreate.go
@@ -121,16 +121,22 @@ func (d *multiPanelCreateData) doValidations(guildId uint64) (panels []database.
}
func (d *multiPanelCreateData) validateTitle() (err error) {
- if len(d.Title) > 255 || len(d.Title) < 1 {
- err = errors.New("embed title must be between 1 and 255 characters")
+ if len(d.Title) > 255 {
+ err = errors.New("Embed title must be between 1 and 255 characters")
+ } else if len(d.Title) == 0 {
+ d.Title = "Click to open a ticket"
}
+
return
}
func (d *multiPanelCreateData) validateContent() (err error) {
- if len(d.Content) > 1024 || len(d.Title) < 1 {
- err = errors.New("embed content must be between 1 and 1024 characters")
+ if len(d.Content) > 1024 {
+ err = errors.New("Embed content must be between 1 and 1024 characters")
+ } else if len(d.Content) == 0 { // Fill default
+ d.Content = "Click on the button corresponding to the type of ticket you wish to open"
}
+
return
}
diff --git a/app/http/endpoints/api/panel/multipanellist.go b/app/http/endpoints/api/panel/multipanellist.go
index 1a9ccce..7f8aac9 100644
--- a/app/http/endpoints/api/panel/multipanellist.go
+++ b/app/http/endpoints/api/panel/multipanellist.go
@@ -12,7 +12,7 @@ import (
func MultiPanelList(ctx *gin.Context) {
type multiPanelResponse struct {
database.MultiPanel
- Panels []database.Panel `json:"panels"`
+ Panels []int `json:"panels"`
}
guildId := ctx.Keys["guildid"].(uint64)
@@ -33,13 +33,20 @@ func MultiPanelList(ctx *gin.Context) {
MultiPanel: multiPanel,
}
+ // TODO: Use a join
group.Go(func() error {
panels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanel.Id)
if err != nil {
return err
}
- data[i].Panels = panels
+ panelIds := make([]int, len(panels))
+ for i, panel := range panels {
+ panelIds[i] = panel.PanelId
+ }
+
+ data[i].Panels = panelIds
+
return nil
})
}
@@ -51,6 +58,6 @@ func MultiPanelList(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"success": true,
- "data": data,
+ "data": data,
})
}
diff --git a/app/http/endpoints/api/panel/panelcreate.go b/app/http/endpoints/api/panel/panelcreate.go
index 240b21d..f51cb7b 100644
--- a/app/http/endpoints/api/panel/panelcreate.go
+++ b/app/http/endpoints/api/panel/panelcreate.go
@@ -26,16 +26,17 @@ 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"`
- Emote string `json:"emote"`
- WelcomeMessage *string `json:"welcome_message"`
- Mentions []string `json:"mentions"`
- Teams []string `json:"teams"`
+ 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"`
+ Emote string `json:"emote"`
+ WelcomeMessage *string `json:"welcome_message"`
+ Mentions []string `json:"mentions"`
+ WithDefaultTeam bool `json:"default_team"`
+ Teams []database.SupportTeam `json:"teams"`
}
func CreatePanel(ctx *gin.Context) {
@@ -120,7 +121,7 @@ func CreatePanel(ctx *gin.Context) {
TargetCategory: data.CategoryId,
ReactionEmote: emoji,
WelcomeMessage: data.WelcomeMessage,
- WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
+ WithDefaultTeam: data.WithDefaultTeam,
CustomId: customId,
}
@@ -179,31 +180,22 @@ func CreatePanel(ctx *gin.Context) {
}
// returns (response_code, error)
-func insertTeams(guildId uint64, panelId int, teamIds []string) (int, error) {
+func insertTeams(guildId uint64, panelId int, teams []database.SupportTeam) (int, error) {
// insert teams
group, _ := errgroup.WithContext(context.Background())
- for _, teamId := range teamIds {
- if teamId == "default" {
- continue // already handled
- }
-
- teamId, err := strconv.Atoi(teamId)
- if err != nil {
- return 400, err
- }
-
+ for _, team := range teams {
group.Go(func() error {
// ensure team exists
- exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
+ exists, err := dbclient.Client.SupportTeam.Exists(team.Id, guildId)
if err != nil {
return err
}
if !exists {
- return fmt.Errorf("team with id %d not found", teamId)
+ return fmt.Errorf("team with id %d not found", team.Id)
}
- return dbclient.Client.PanelTeams.Add(panelId, teamId)
+ return dbclient.Client.PanelTeams.Add(panelId, team.Id)
})
}
@@ -266,11 +258,23 @@ func (p *panelBody) doValidations(ctx *gin.Context, guildId uint64) bool {
}
func (p *panelBody) verifyTitle() bool {
- return len(p.Title) > 0 && len(p.Title) <= 80
+ if len(p.Title) > 80 {
+ return false
+ } else if len(p.Title) == 0 { // Fill default
+ p.Title = "Open a ticket!"
+ }
+
+ return true
}
func (p *panelBody) verifyContent() bool {
- return len(p.Content) > 0 && len(p.Content) < 1025
+ if len(p.Content) > 1024 {
+ return false
+ } else if len(p.Content) == 0 { // Fill default
+ p.Content = "By clicking the button, a ticket will be opened for you."
+ }
+
+ return true
}
func (p *panelBody) getEmoji() (emoji string, ok bool) {
diff --git a/app/http/endpoints/api/panel/panelupdate.go b/app/http/endpoints/api/panel/panelupdate.go
index a9b5f22..bcb47a2 100644
--- a/app/http/endpoints/api/panel/panelupdate.go
+++ b/app/http/endpoints/api/panel/panelupdate.go
@@ -1,7 +1,6 @@
package api
import (
- "context"
"errors"
"github.com/TicketsBot/GoPanel/botcontext"
dbclient "github.com/TicketsBot/GoPanel/database"
@@ -12,9 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
"github.com/rxdn/gdl/rest/request"
- "golang.org/x/sync/errgroup"
"strconv"
- "sync"
)
func UpdatePanel(ctx *gin.Context) {
@@ -67,48 +64,39 @@ func UpdatePanel(ctx *gin.Context) {
return
}
- var wouldHaveDuplicateEmote bool
+ premiumTier := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
- {
- var duplicateLock sync.Mutex
-
- group, _ := errgroup.WithContext(context.Background())
- for _, multiPanelId := range multiPanels {
- multiPanelId := multiPanelId
-
- group.Go(func() error {
- // get the sub-panels of the multi-panel
- subPanels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanelId)
- if err != nil {
- return err
- }
-
- for _, subPanel := range subPanels {
- if subPanel.MessageId == existing.MessageId {
- continue
- }
-
- if subPanel.ReactionEmote == data.Emote {
- duplicateLock.Lock()
- wouldHaveDuplicateEmote = true
- duplicateLock.Unlock()
- break
- }
- }
-
- return nil
- })
- }
-
- if err := group.Wait(); err != nil {
+ for _, multiPanel := range multiPanels {
+ panels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanel.Id)
+ if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
- }
- if wouldHaveDuplicateEmote {
- ctx.JSON(400, utils.ErrorJson(errors.New("Changing the reaction emote to this value would cause a conflict in a multi-panel")))
- return
+ // TODO: Optimise this
+ panelIds := make([]int, len(panels))
+ for i, panel := range panels {
+ panelIds[i] = panel.PanelId
+ }
+
+ data := multiPanelCreateData{
+ Title: multiPanel.Title,
+ Content: multiPanel.Content,
+ Colour: int32(multiPanel.Colour),
+ ChannelId: multiPanel.ChannelId,
+ Panels: panelIds,
+ }
+
+ messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None, panels)
+ if err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
+
+ if err := dbclient.Client.MultiPanels.UpdateMessageId(multiPanel.Id, messageId); err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
}
// check if we need to update the message
@@ -125,7 +113,6 @@ func UpdatePanel(ctx *gin.Context) {
// delete old message, ignoring error
_ = rest.DeleteMessage(botContext.Token, botContext.RateLimiter, existing.ChannelId, existing.MessageId)
- premiumTier := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
newMessageId, err = data.sendEmbed(&botContext, existing.Title, existing.CustomId, existing.ReactionEmote, premiumTier > premium.None)
if err != nil {
var unwrapped request.RestError
@@ -155,7 +142,7 @@ func UpdatePanel(ctx *gin.Context) {
TargetCategory: data.CategoryId,
ReactionEmote: emoji,
WelcomeMessage: data.WelcomeMessage,
- WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
+ WithDefaultTeam: data.WithDefaultTeam,
CustomId: existing.CustomId,
}
diff --git a/app/http/endpoints/api/premium.go b/app/http/endpoints/api/premium.go
index d30672a..0d2f9a2 100644
--- a/app/http/endpoints/api/premium.go
+++ b/app/http/endpoints/api/premium.go
@@ -14,7 +14,7 @@ func PremiumHandler(ctx *gin.Context) {
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
- "error": err.Error(),
+ "error": err.Error(),
})
return
}
@@ -23,5 +23,6 @@ func PremiumHandler(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"premium": premiumTier >= premium.Premium,
+ "tier": premiumTier,
})
}
diff --git a/app/http/endpoints/api/reloadguilds.go b/app/http/endpoints/api/reloadguilds.go
index 60989be..fcfd1a6 100644
--- a/app/http/endpoints/api/reloadguilds.go
+++ b/app/http/endpoints/api/reloadguilds.go
@@ -2,10 +2,10 @@ package api
import (
"fmt"
+ "github.com/TicketsBot/GoPanel/app/http/session"
"github.com/TicketsBot/GoPanel/messagequeue"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/GoPanel/utils/discord"
- "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"time"
)
@@ -36,19 +36,22 @@ func ReloadGuildsHandler(ctx *gin.Context) {
return
}
- store := sessions.Default(ctx)
- if store == nil {
- ctx.JSON(200, gin.H{
- "success": false,
- "reauthenticate_required": true,
- })
+ store, err := session.Store.Get(userId)
+ if err != nil {
+ if err == session.ErrNoSession {
+ ctx.JSON(401, gin.H{
+ "success": false,
+ "auth": true,
+ })
+ } else {
+ ctx.JSON(500, utils.ErrorJson(err))
+ }
+
return
}
- accessToken := store.Get("access_token").(string)
- expiry := store.Get("expiry").(int64)
- if expiry > (time.Now().UnixNano() / int64(time.Second)) {
- res, err := discord.RefreshToken(store.Get("refresh_token").(string))
+ if store.Expiry > (time.Now().UnixNano() / int64(time.Second)) {
+ res, err := discord.RefreshToken(store.RefreshToken)
if err != nil { // Tell client to re-authenticate
ctx.JSON(200, gin.H{
"success": false,
@@ -57,15 +60,17 @@ func ReloadGuildsHandler(ctx *gin.Context) {
return
}
- accessToken = res.AccessToken
+ store.AccessToken = res.AccessToken
+ store.RefreshToken = res.RefreshToken
+ store.Expiry = (time.Now().UnixNano()/int64(time.Second))+int64(res.ExpiresIn)
- store.Set("access_token", res.AccessToken)
- store.Set("refresh_token", res.RefreshToken)
- store.Set("expiry", (time.Now().UnixNano()/int64(time.Second))+int64(res.ExpiresIn))
- store.Save()
+ if err := session.Store.Set(userId, store); err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
}
- if err := utils.LoadGuilds(accessToken, userId); err != nil {
+ if err := utils.LoadGuilds(store.AccessToken, userId); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
diff --git a/app/http/endpoints/api/searchmembers.go b/app/http/endpoints/api/searchmembers.go
index 7d2d191..f687e28 100644
--- a/app/http/endpoints/api/searchmembers.go
+++ b/app/http/endpoints/api/searchmembers.go
@@ -4,6 +4,7 @@ import (
"github.com/TicketsBot/GoPanel/botcontext"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
+ "github.com/rxdn/gdl/objects/member"
)
func SearchMembers(ctx *gin.Context) {
@@ -16,12 +17,18 @@ func SearchMembers(ctx *gin.Context) {
}
query := ctx.Query("query")
- if len(query) == 0 || len(query) > 32 {
+ if len(query) > 32 {
ctx.JSON(400, utils.ErrorStr("Invalid query"))
return
}
- members, err := botCtx.SearchMembers(guildId, query)
+ var members []member.Member
+ if query == "" {
+ members, err = botCtx.ListMembers(guildId)
+ } else {
+ members, err = botCtx.SearchMembers(guildId, query)
+ }
+
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
diff --git a/app/http/endpoints/api/session.go b/app/http/endpoints/api/session.go
new file mode 100644
index 0000000..94e2411
--- /dev/null
+++ b/app/http/endpoints/api/session.go
@@ -0,0 +1,31 @@
+package api
+
+import (
+ "github.com/TicketsBot/GoPanel/app/http/session"
+ "github.com/TicketsBot/GoPanel/utils"
+ "github.com/gin-gonic/gin"
+)
+
+func SessionHandler(ctx *gin.Context) {
+ userId := ctx.Keys["userid"].(uint64)
+
+ store, err := session.Store.Get(userId)
+ if err != nil {
+ if err == session.ErrNoSession {
+ ctx.JSON(404, gin.H{
+ "success": false,
+ "error": err.Error(),
+ "auth": true,
+ })
+ } else {
+ ctx.JSON(500, utils.ErrorJson(err))
+ }
+
+ return
+ }
+
+ ctx.JSON(200, gin.H{
+ "username": store.Name,
+ "avatar": store.Avatar,
+ })
+}
diff --git a/app/http/endpoints/api/settings/settings.go b/app/http/endpoints/api/settings/settings.go
index 5dddba6..9f2de6a 100644
--- a/app/http/endpoints/api/settings/settings.go
+++ b/app/http/endpoints/api/settings/settings.go
@@ -30,18 +30,30 @@ func GetSettingsHandler(ctx *gin.Context) {
// prefix
group.Go(func() (err error) {
settings.Prefix, err = dbclient.Client.Prefix.Get(guildId)
+ if err == nil && settings.Prefix == "" {
+ settings.Prefix = "t!"
+ }
+
return
})
// welcome message
group.Go(func() (err error) {
settings.WelcomeMessaage, err = dbclient.Client.WelcomeMessages.Get(guildId)
+ if err == nil && settings.WelcomeMessaage == "" {
+ settings.WelcomeMessaage = "Thank you for contacting support.\nPlease describe your issue and await a response."
+ }
+
return
})
// ticket limit
group.Go(func() (err error) {
settings.TicketLimit, err = dbclient.Client.TicketLimit.Get(guildId)
+ if err == nil && settings.TicketLimit == 0 {
+ settings.TicketLimit = 5 // Set default
+ }
+
return
})
diff --git a/app/http/endpoints/api/team/getmembers.go b/app/http/endpoints/api/team/getmembers.go
index df802da..fe61b9c 100644
--- a/app/http/endpoints/api/team/getmembers.go
+++ b/app/http/endpoints/api/team/getmembers.go
@@ -114,7 +114,7 @@ func formatMembers(guildId uint64, userIds, roleIds []uint64) ([]entity, error)
}
// map role ids to names
- var data []entity
+ data := make([]entity, 0)
for _, roleId := range roleIds {
for _, role := range roles {
if roleId == role.Id {
diff --git a/app/http/endpoints/api/ticket/getticket.go b/app/http/endpoints/api/ticket/getticket.go
index 0adc842..dff8c1a 100644
--- a/app/http/endpoints/api/ticket/getticket.go
+++ b/app/http/endpoints/api/ticket/getticket.go
@@ -86,7 +86,7 @@ func GetTicket(ctx *gin.Context) {
}
messagesFormatted = append(messagesFormatted, map[string]interface{}{
- "username": message.Author.Username,
+ "author": message.Author,
"content": content,
})
}
diff --git a/app/http/endpoints/api/ticket/gettickets.go b/app/http/endpoints/api/ticket/gettickets.go
index ec4b63e..6d3a5d6 100644
--- a/app/http/endpoints/api/ticket/gettickets.go
+++ b/app/http/endpoints/api/ticket/gettickets.go
@@ -2,27 +2,31 @@ package api
import (
"context"
- "fmt"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin"
+ "github.com/rxdn/gdl/objects/user"
"golang.org/x/sync/errgroup"
- "strconv"
)
func GetTickets(ctx *gin.Context) {
+ type WithUser struct {
+ TicketId int `json:"id"`
+ User *user.User `json:"user,omitempty"`
+ }
+
guildId := ctx.Keys["guildid"].(uint64)
tickets, err := database.Client.Tickets.GetGuildOpenTickets(guildId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
- "error": err.Error(),
+ "error": err.Error(),
})
return
}
- ticketsFormatted := make([]map[string]interface{}, len(tickets))
+ data := make([]WithUser, len(tickets))
group, _ := errgroup.WithContext(context.Background())
@@ -31,29 +35,14 @@ func GetTickets(ctx *gin.Context) {
ticket := ticket
group.Go(func() error {
- members, err := database.Client.TicketMembers.Get(guildId, ticket.Id)
- if err != nil {
- return err
+ user, ok := cache.Instance.GetUser(ticket.UserId)
+
+ data[i] = WithUser{
+ TicketId: ticket.Id,
}
- membersFormatted := make([]map[string]interface{}, 0)
- for _, userId := range members {
- user, _ := cache.Instance.GetUser(userId)
-
- membersFormatted = append(membersFormatted, map[string]interface{}{
- "id": strconv.FormatUint(userId, 10),
- "username": user.Username,
- "discrim": fmt.Sprintf("%04d", user.Discriminator),
- })
- }
-
- owner, _ := cache.Instance.GetUser(ticket.UserId)
-
- ticketsFormatted[len(tickets) - 1 - i] = map[string]interface{}{
- "ticketId": ticket.Id,
- "username": owner.Username,
- "discrim": fmt.Sprintf("%04d", owner.Discriminator),
- "members": membersFormatted,
+ if ok {
+ data[i].User = &user
}
return nil
@@ -63,10 +52,10 @@ func GetTickets(ctx *gin.Context) {
if err := group.Wait(); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
- "error": err.Error(),
+ "error": err.Error(),
})
return
}
- ctx.JSON(200, ticketsFormatted)
+ ctx.JSON(200, data)
}
diff --git a/app/http/endpoints/api/token.go b/app/http/endpoints/api/token.go
deleted file mode 100644
index c5b8f50..0000000
--- a/app/http/endpoints/api/token.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package api
-
-import (
- "fmt"
- "github.com/TicketsBot/GoPanel/config"
- "github.com/TicketsBot/GoPanel/utils"
- "github.com/dgrijalva/jwt-go"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
- "strconv"
- "time"
-)
-
-func TokenHandler(ctx *gin.Context) {
- session := sessions.Default(ctx)
- userId := utils.GetUserId(session)
-
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
- "userid": strconv.FormatUint(userId, 10),
- "timestamp": time.Now(),
- })
-
- str, err := token.SignedString([]byte(config.Conf.Server.Secret))
- if err != nil {
- fmt.Println(err.Error())
- ctx.JSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- } else {
- ctx.JSON(200, gin.H{
- "success": true,
- "token": str,
- })
- }
-}
diff --git a/app/http/endpoints/api/transcripts/get.go b/app/http/endpoints/api/transcripts/get.go
new file mode 100644
index 0000000..33786fc
--- /dev/null
+++ b/app/http/endpoints/api/transcripts/get.go
@@ -0,0 +1,68 @@
+package api
+
+import (
+ "errors"
+ "github.com/TicketsBot/GoPanel/database"
+ "github.com/TicketsBot/GoPanel/utils"
+ "github.com/TicketsBot/archiverclient"
+ "github.com/TicketsBot/common/permission"
+ "github.com/gin-gonic/gin"
+ "strconv"
+)
+
+func GetTranscriptHandler(ctx *gin.Context) {
+ guildId := ctx.Keys["guildid"].(uint64)
+ userId := ctx.Keys["userid"].(uint64)
+
+ // format ticket ID
+ ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
+ if err != nil {
+ ctx.JSON(400, utils.ErrorStr("Invalid ticket ID"))
+ return
+ }
+
+ // get ticket object
+ ticket, err := database.Client.Tickets.Get(ticketId, guildId)
+ if err != nil {
+ // TODO: 500 error page
+ ctx.AbortWithStatusJSON(500, gin.H{
+ "success": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ // Verify this is a valid ticket and it is closed
+ if ticket.UserId == 0 || ticket.Open {
+ ctx.JSON(404, utils.ErrorStr("Transcript not found"))
+ return
+ }
+
+ // Verify the user has permissions to be here
+ if ticket.UserId != userId {
+ permLevel, err := utils.GetPermissionLevel(guildId, userId)
+ if err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
+
+ if permLevel < permission.Support {
+ ctx.JSON(403, utils.ErrorStr("You do not have permission to view this transcript"))
+ return
+ }
+ }
+
+ // retrieve ticket messages from bucket
+ messages, err := utils.ArchiverClient.Get(guildId, ticketId)
+ if err != nil {
+ if errors.Is(err, archiverclient.ErrExpired) {
+ ctx.JSON(404, utils.ErrorStr("Transcript not found"))
+ } else {
+ ctx.JSON(500, utils.ErrorJson(err))
+ }
+
+ return
+ }
+
+ ctx.JSON(200, messages)
+}
diff --git a/app/http/endpoints/api/transcripts/list.go b/app/http/endpoints/api/transcripts/list.go
new file mode 100644
index 0000000..252efe5
--- /dev/null
+++ b/app/http/endpoints/api/transcripts/list.go
@@ -0,0 +1,247 @@
+package api
+
+import (
+ "errors"
+ "github.com/TicketsBot/GoPanel/botcontext"
+ dbclient "github.com/TicketsBot/GoPanel/database"
+ "github.com/TicketsBot/GoPanel/rpc/cache"
+ "github.com/TicketsBot/GoPanel/utils"
+ "github.com/TicketsBot/database"
+ "github.com/gin-gonic/gin"
+ "math"
+ "net/http"
+ "strconv"
+)
+
+const (
+ pageLimit = 30
+)
+
+type filterType uint8
+
+const (
+ filterTypeNone filterType = iota
+ filterTypeTicketId
+ filterTypeUsername
+ filterTypeUserId
+)
+
+type transcript struct {
+ TicketId int `json:"ticket_id"`
+ Username string `json:"username"`
+ CloseReason *string `json:"close_reason"`
+}
+
+func ListTranscripts(ctx *gin.Context) {
+ guildId := ctx.Keys["guildid"].(uint64)
+
+ botContext, err := botcontext.ContextForGuild(guildId)
+ if err != nil {
+ ctx.AbortWithStatusJSON(500, gin.H{
+ "success": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ // db functions will handle if 0
+ before, _ := strconv.Atoi(ctx.Query("before"))
+ after, _ := strconv.Atoi(ctx.Query("after"))
+
+ var tickets []database.TicketWithCloseReason
+ var status int
+
+ filterType := getFilterType(ctx)
+ switch filterType {
+ case filterTypeNone:
+ tickets, status, err = getTickets(guildId, before, after)
+ case filterTypeTicketId:
+ tickets, status, err = getTicketsByTicketId(guildId, ctx)
+ case filterTypeUsername:
+ tickets, status, err = getTicketsByUsername(guildId, before, after, ctx)
+ case filterTypeUserId:
+ tickets, status, err = getTicketsByUserId(guildId, before, after, ctx)
+ }
+
+ if err != nil {
+ ctx.JSON(status, utils.ErrorJson(err))
+ return
+ }
+
+ // Create a mapping user_id -> username so we can skip duplicates
+ usernames := make(map[uint64]string)
+ for _, ticket := range tickets {
+ if _, ok := usernames[ticket.UserId]; ok {
+ continue // don't fetch again
+ }
+
+ // check cache, for some reason botContext.GetUser doesn't do this
+ user, ok := cache.Instance.GetUser(ticket.UserId)
+ if ok {
+ usernames[ticket.UserId] = user.Username
+ } else {
+ user, err = botContext.GetUser(ticket.UserId)
+ if err != nil { // TODO: Log
+ usernames[ticket.UserId] = "Unknown User"
+ } else {
+ usernames[ticket.UserId] = user.Username
+ }
+ }
+ }
+
+ transcripts := make([]transcript, len(tickets))
+ for i, ticket := range tickets {
+ transcripts[i] = transcript{
+ TicketId: ticket.Id,
+ Username: usernames[ticket.UserId],
+ CloseReason: ticket.CloseReason,
+ }
+ }
+
+ ctx.JSON(200, transcripts)
+}
+
+func getFilterType(ctx *gin.Context) filterType {
+ if ctx.Query("ticketid") != "" {
+ return filterTypeTicketId
+ } else if ctx.Query("username") != "" {
+ return filterTypeUsername
+ } else if ctx.Query("userid") != "" {
+ return filterTypeUserId
+ } else {
+ return filterTypeNone
+ }
+}
+
+func getTickets(guildId uint64, before, after int) ([]database.TicketWithCloseReason, int, error) {
+ var tickets []database.TicketWithCloseReason
+ var err error
+
+ if before <= 0 && after <= 0 {
+ tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsBeforeWithCloseReason(guildId, pageLimit, math.MaxInt32)
+ } else if before > 0 {
+ tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsBeforeWithCloseReason(guildId, pageLimit, before)
+ } else { // after > 0
+ // returns in ascending order, must reverse
+ tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsAfterWithCloseReason(guildId, pageLimit, after)
+ if err == nil {
+ reverse(tickets)
+ }
+ }
+
+
+ status := http.StatusOK
+ if err != nil {
+ status = http.StatusInternalServerError
+ }
+
+ return tickets, status, err
+}
+
+// (tickets, statusCode, error)
+func getTicketsByTicketId(guildId uint64, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) {
+ ticketId, err := strconv.Atoi(ctx.Query("ticketid"))
+ if err != nil {
+ return nil, 400, err
+ }
+
+ ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId)
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ if ticket.Id == 0 {
+ return nil, http.StatusNotFound, errors.New("ticket not found")
+ }
+
+ closeReason, ok, err := dbclient.Client.CloseReason.Get(guildId, ticketId)
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ data := database.TicketWithCloseReason{
+ Ticket: ticket,
+ }
+
+ if ok {
+ data.CloseReason = &closeReason
+ }
+
+ return []database.TicketWithCloseReason{data}, http.StatusOK, nil
+}
+
+// (tickets, statusCode, error)
+func getTicketsByUsername(guildId uint64, before, after int, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) {
+ username := ctx.Query("username")
+
+ botContext, err := botcontext.ContextForGuild(guildId)
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ members, err := botContext.SearchMembers(guildId, username)
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ userIds := make([]uint64, len(members)) // capped at 100
+ for i, member := range members {
+ userIds[i] = member.User.Id
+ }
+
+ var tickets []database.TicketWithCloseReason
+ if before <= 0 && after <= 0 {
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, userIds, math.MaxInt32, pageLimit)
+ } else if before > 0 {
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, userIds, before, pageLimit)
+ } else { // after > 0
+ // returns in ascending order, must reverse
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyAfterWithCloseReason(guildId, userIds, after, pageLimit)
+ if err == nil {
+ reverse(tickets)
+ }
+ }
+
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ return tickets, http.StatusOK, nil
+}
+
+// (tickets, statusCode, error)
+func getTicketsByUserId(guildId uint64, before, after int, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) {
+ userId, err := strconv.ParseUint(ctx.Query("userid"), 10, 64)
+ if err != nil {
+ return nil, 400, err
+ }
+
+ var tickets []database.TicketWithCloseReason
+ if before <= 0 && after <= 0 {
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, []uint64{userId}, math.MaxInt32, pageLimit)
+ } else if before > 0 {
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, []uint64{userId}, before, pageLimit)
+ } else { // after > 0
+ // returns in ascending order, must reverse
+ tickets, err = dbclient.Client.Tickets.GetClosedByAnyAfterWithCloseReason(guildId, []uint64{userId}, after, pageLimit)
+ if err == nil {
+ reverse(tickets)
+ }
+ }
+
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+
+ return tickets, http.StatusOK, nil
+}
+
+func reverse(slice []database.TicketWithCloseReason) {
+ if len(slice) == 0 {
+ return
+ }
+
+ for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
+ slice[i], slice[j] = slice[j], slice[i]
+ }
+}
diff --git a/app/http/endpoints/manage/blacklist.go b/app/http/endpoints/manage/blacklist.go
deleted file mode 100644
index 226349d..0000000
--- a/app/http/endpoints/manage/blacklist.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func BlacklistHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/blacklist", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/logslist.go b/app/http/endpoints/manage/logslist.go
deleted file mode 100644
index ab1d0b6..0000000
--- a/app/http/endpoints/manage/logslist.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func LogsHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/logs", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/logsview.go b/app/http/endpoints/manage/logsview.go
deleted file mode 100644
index 98c5839..0000000
--- a/app/http/endpoints/manage/logsview.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package manage
-
-import (
- "errors"
- "fmt"
- "github.com/TicketsBot/GoPanel/config"
- "github.com/TicketsBot/GoPanel/database"
- "github.com/TicketsBot/GoPanel/rpc/cache"
- "github.com/TicketsBot/GoPanel/utils"
- "github.com/TicketsBot/archiverclient"
- "github.com/TicketsBot/common/permission"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
- "strconv"
-)
-
-var Archiver archiverclient.ArchiverClient
-
-func LogViewHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- if store == nil {
- return
- }
-
- if utils.IsLoggedIn(store) {
- userId := utils.GetUserId(store)
-
- // Verify the guild exists
- guildId, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
- if err != nil {
- ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
- return
- }
-
- // Get object for selected guild
- guild, _ := cache.Instance.GetGuild(guildId, false)
-
- // format ticket ID
- ticketId, err := strconv.Atoi(ctx.Param("ticket")); if err != nil {
- ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id))
- return
- }
-
- // get ticket object
- ticket, err := database.Client.Tickets.Get(ticketId, guildId)
- if err != nil {
- // TODO: 500 error page
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
-
- // Verify this is a valid ticket and it is closed
- if ticket.UserId == 0 || ticket.Open {
- ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id))
- return
- }
-
- // Verify the user has permissions to be here
- if ticket.UserId != userId {
- permLevel, err := utils.GetPermissionLevel(guildId, userId)
- if err != nil {
- ctx.JSON(500, utils.ErrorJson(err))
- return
- }
-
- if permLevel < permission.Support {
- ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
- return
- }
- }
-
- // retrieve ticket messages from bucket
- messages, err := Archiver.Get(guildId, ticketId)
- if err != nil {
- if errors.Is(err, archiverclient.ErrExpired) {
- ctx.String(200, "Failed to retrieve archive - please contact the developers quoting error code: ErrExpired") // TODO: Actual error page
- return
- }
-
- ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
- return
- }
-
- // format to html
- html, err := Archiver.Encode(messages, fmt.Sprintf("ticket-%d", ticketId))
- if err != nil {
- ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
- return
- }
-
- ctx.Data(200, gin.MIMEHTML, html)
- } else {
- ctx.Redirect(302, fmt.Sprintf("/login?noguilds&state=viewlog.%s.%s", ctx.Param("id"), ctx.Param("ticket")))
- }
-}
diff --git a/app/http/endpoints/manage/panels.go b/app/http/endpoints/manage/panels.go
deleted file mode 100644
index 3786196..0000000
--- a/app/http/endpoints/manage/panels.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func PanelHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/panels", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/settings.go b/app/http/endpoints/manage/settings.go
deleted file mode 100644
index cddfa58..0000000
--- a/app/http/endpoints/manage/settings.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func SettingsHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/settings", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/tags.go b/app/http/endpoints/manage/tags.go
deleted file mode 100644
index d2a4f13..0000000
--- a/app/http/endpoints/manage/tags.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func TagsHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/tags", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/ticketlist.go b/app/http/endpoints/manage/ticketlist.go
deleted file mode 100644
index f49b78e..0000000
--- a/app/http/endpoints/manage/ticketlist.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func TicketListHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/ticketlist", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- })
-}
diff --git a/app/http/endpoints/manage/ticketview.go b/app/http/endpoints/manage/ticketview.go
deleted file mode 100644
index 61fe34d..0000000
--- a/app/http/endpoints/manage/ticketview.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package manage
-
-import (
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func TicketViewHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- guildId := ctx.Keys["guildid"].(uint64)
-
- ctx.HTML(200, "manage/ticketview", gin.H{
- "name": store.Get("name").(string),
- "guildId": guildId,
- "avatar": store.Get("avatar").(string),
- "baseUrl": config.Conf.Server.BaseUrl,
- "ticketId": ctx.Param("ticketId"),
- })
-}
diff --git a/app/http/endpoints/manage/webchatws.go b/app/http/endpoints/manage/webchatws.go
deleted file mode 100644
index 1cdf81a..0000000
--- a/app/http/endpoints/manage/webchatws.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package manage
-
-import (
- "fmt"
- "github.com/TicketsBot/GoPanel/botcontext"
- "github.com/TicketsBot/GoPanel/rpc"
- "github.com/TicketsBot/GoPanel/utils"
- "github.com/TicketsBot/common/permission"
- "github.com/TicketsBot/common/premium"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
- "github.com/gorilla/websocket"
- "strconv"
- "sync"
-)
-
-var upgrader = websocket.Upgrader{
- ReadBufferSize: 1024,
- WriteBufferSize: 1024,
-}
-
-var SocketsLock sync.Mutex
-var Sockets []*Socket
-
-type (
- Socket struct {
- Ws *websocket.Conn
- Guild string
- Ticket int
- }
-
- WsEvent struct {
- Type string
- Data interface{}
- }
-
- AuthEvent struct {
- Guild string
- Ticket string
- }
-)
-
-func WebChatWs(ctx *gin.Context) {
- store := sessions.Default(ctx)
-
- conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
- if err != nil {
- fmt.Println(err.Error())
- return
- }
-
- socket := &Socket{
- Ws: conn,
- }
-
- conn.SetCloseHandler(func(code int, text string) error {
- i := -1
- SocketsLock.Lock()
-
- for index, element := range Sockets {
- if element == socket {
- i = index
- break
- }
- }
-
- if i != -1 {
- Sockets = Sockets[:i+copy(Sockets[i:], Sockets[i+1:])]
- }
- SocketsLock.Unlock()
-
- return nil
- })
-
- SocketsLock.Lock()
- Sockets = append(Sockets, socket)
- SocketsLock.Unlock()
- userId := utils.GetUserId(store)
-
- var guildId string
- var guildIdParsed uint64
- var ticket int
-
- for {
- var evnt WsEvent
- err := conn.ReadJSON(&evnt)
- if err != nil {
- break
- }
-
- if guildId == "" && evnt.Type != "auth" {
- conn.Close()
- break
- } else if evnt.Type == "auth" {
- data := evnt.Data.(map[string]interface{})
-
- guildId = data["guild"].(string)
- ticket, err = strconv.Atoi(data["ticket"].(string))
- if err != nil {
- conn.Close()
- break
- }
-
- socket.Guild = guildId
- socket.Ticket = ticket
-
- // Verify the guild exists
- guildIdParsed, err = strconv.ParseUint(guildId, 10, 64)
- if err != nil {
- fmt.Println(err.Error())
- conn.Close()
- return
- }
-
- // Verify the user has permissions to be here
- permLevel, err := utils.GetPermissionLevel(guildIdParsed, userId)
- if err != nil {
- fmt.Println(err.Error())
- conn.Close()
- return
- }
-
- if permLevel < permission.Admin {
- fmt.Println(err.Error())
- conn.Close()
- return
- }
-
- botContext, err := botcontext.ContextForGuild(guildIdParsed)
- if err != nil {
- ctx.AbortWithStatusJSON(500, gin.H{
- "success": false,
- "error": err.Error(),
- })
- return
- }
-
- // Verify the guild is premium
- premiumTier := rpc.PremiumClient.GetTierByGuildId(guildIdParsed, true, botContext.Token, botContext.RateLimiter)
- if premiumTier == premium.None {
- conn.Close()
- return
- }
- }
- }
-}
diff --git a/app/http/endpoints/root/callback.go b/app/http/endpoints/root/callback.go
index beb717d..1ed6008 100644
--- a/app/http/endpoints/root/callback.go
+++ b/app/http/endpoints/root/callback.go
@@ -2,14 +2,14 @@ package root
import (
"fmt"
+ "github.com/TicketsBot/GoPanel/app/http/session"
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/GoPanel/utils/discord"
- "github.com/apex/log"
- "github.com/gin-gonic/contrib/sessions"
+ "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
- "strings"
+ "strconv"
"time"
)
@@ -33,63 +33,59 @@ type (
)
func CallbackHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- if store == nil {
- return
- }
- defer store.Save()
-
- if utils.IsLoggedIn(store) && store.Get("has_guilds") == true {
- ctx.Redirect(302, config.Conf.Server.BaseUrl)
- return
- }
-
code, ok := ctx.GetQuery("code")
if !ok {
- utils.ErrorPage(ctx, 400, "Discord provided invalid Oauth2 code")
+ ctx.JSON(400, utils.ErrorStr("Discord provided invalid Oauth2 code"))
return
}
res, err := discord.AccessToken(code)
if err != nil {
- utils.ErrorPage(ctx, 500, err.Error())
+ ctx.JSON(500, utils.ErrorJson(err))
return
}
- store.Set("access_token", res.AccessToken)
- store.Set("refresh_token", res.RefreshToken)
- store.Set("expiry", (time.Now().UnixNano()/int64(time.Second))+int64(res.ExpiresIn))
-
// Get ID + name
currentUser, err := rest.GetCurrentUser(fmt.Sprintf("Bearer %s", res.AccessToken), nil)
if err != nil {
- ctx.String(500, err.Error())
+ ctx.JSON(500, utils.ErrorJson(err))
return
}
- store.Set("csrf", utils.RandString(32))
-
- store.Set("userid", currentUser.Id)
- store.Set("name", currentUser.Username)
- store.Set("avatar", currentUser.AvatarUrl(256))
- store.Save()
+ store := session.SessionData{
+ AccessToken: res.AccessToken,
+ Expiry: (time.Now().UnixNano()/int64(time.Second))+int64(res.ExpiresIn),
+ RefreshToken: res.RefreshToken,
+ Name: currentUser.Username,
+ Avatar: currentUser.AvatarUrl(256),
+ HasGuilds: false,
+ }
if err := utils.LoadGuilds(res.AccessToken, currentUser.Id); err == nil {
- store.Set("has_guilds", true)
- store.Save()
+ store.HasGuilds = true
} else {
- log.Error(err.Error())
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
}
- handleRedirect(ctx)
-}
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
+ "userid": strconv.FormatUint(currentUser.Id, 10),
+ "timestamp": time.Now(),
+ })
-func handleRedirect(ctx *gin.Context) {
- state := strings.Split(ctx.Query("state"), ".")
-
- if len(state) == 3 && state[0] == "viewlog" {
- ctx.Redirect(302, fmt.Sprintf("%s/manage/%s/logs/view/%s", config.Conf.Server.BaseUrl, state[1], state[2]))
- } else {
- ctx.Redirect(302, config.Conf.Server.BaseUrl)
+ str, err := token.SignedString([]byte(config.Conf.Server.Secret))
+ if err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
}
+
+ if err := session.Store.Set(currentUser.Id, store); err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
+ return
+ }
+
+ ctx.JSON(200, gin.H{
+ "success": true,
+ "token": str,
+ })
}
diff --git a/app/http/endpoints/root/index.go b/app/http/endpoints/root/index.go
deleted file mode 100644
index 3d6d746..0000000
--- a/app/http/endpoints/root/index.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package root
-
-import (
- "fmt"
- "github.com/TicketsBot/GoPanel/config"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
- "net/url"
-)
-
-func IndexHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
-
- if _, hasGuilds := store.Get("has_guilds").(bool); !hasGuilds {
- redirect := url.QueryEscape(config.Conf.Oauth.RedirectUri)
- ctx.Redirect(302, fmt.Sprintf("https://discordapp.com/oauth2/authorize?response_type=code&redirect_uri=%s&scope=identify+guilds&client_id=%d&state=%s", redirect, config.Conf.Oauth.Id, ctx.Query("state")))
- return
- }
-
- ctx.HTML(200, "main/index", gin.H{
- "name": store.Get("name").(string),
- "baseurl": config.Conf.Server.BaseUrl,
- "avatar": store.Get("avatar").(string),
- "referralShow": config.Conf.Referral.Show,
- "referralLink": config.Conf.Referral.Link,
- })
-}
diff --git a/app/http/endpoints/root/login.go b/app/http/endpoints/root/login.go
deleted file mode 100644
index ea311b8..0000000
--- a/app/http/endpoints/root/login.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package root
-
-import (
- "fmt"
- "github.com/TicketsBot/GoPanel/config"
- "github.com/TicketsBot/GoPanel/utils"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
- "net/url"
-)
-
-func LoginHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- if store == nil {
- return
- }
- defer store.Save()
-
- if utils.IsLoggedIn(store) {
- ctx.Redirect(302, config.Conf.Server.BaseUrl)
- } else {
- redirect := url.QueryEscape(config.Conf.Oauth.RedirectUri)
-
- var guildsScope string
- if _, noGuilds := ctx.GetQuery("noguilds"); !noGuilds {
- guildsScope = "+guilds"
- }
-
- ctx.Redirect(302, fmt.Sprintf("https://discordapp.com/oauth2/authorize?response_type=code&redirect_uri=%s&scope=identify%s&client_id=%d&state=%s", redirect, guildsScope, config.Conf.Oauth.Id, ctx.Query("state")))
- }
-}
diff --git a/app/http/endpoints/root/logout.go b/app/http/endpoints/root/logout.go
index 50d46f9..861798a 100644
--- a/app/http/endpoints/root/logout.go
+++ b/app/http/endpoints/root/logout.go
@@ -1,18 +1,18 @@
package root
import (
- "github.com/gin-gonic/contrib/sessions"
+ "github.com/TicketsBot/GoPanel/app/http/session"
+ "github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
)
func LogoutHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- if store == nil {
+ userId := ctx.Keys["userid"].(uint64)
+
+ if err := session.Store.Clear(userId); err != nil {
+ ctx.JSON(500, utils.ErrorJson(err))
return
}
- defer store.Save()
- store.Clear()
-
- ctx.Redirect(302, "https://ticketsbot.net")
+ ctx.Status(204)
}
diff --git a/app/http/endpoints/root/webchatws.go b/app/http/endpoints/root/webchatws.go
new file mode 100644
index 0000000..b3cee82
--- /dev/null
+++ b/app/http/endpoints/root/webchatws.go
@@ -0,0 +1,193 @@
+package root
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/TicketsBot/GoPanel/botcontext"
+ "github.com/TicketsBot/GoPanel/config"
+ "github.com/TicketsBot/GoPanel/rpc"
+ "github.com/TicketsBot/GoPanel/utils"
+ "github.com/TicketsBot/common/permission"
+ "github.com/TicketsBot/common/premium"
+ "github.com/dgrijalva/jwt-go"
+ "github.com/gin-gonic/gin"
+ "github.com/gorilla/websocket"
+ "net/http"
+ "strconv"
+ "sync"
+ "time"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ CheckOrigin: func(r *http.Request) bool {
+ return r.Header.Get("Origin") == config.Conf.Server.BaseUrl
+ },
+}
+
+var SocketsLock sync.RWMutex
+var Sockets []*Socket
+
+type (
+ Socket struct {
+ Ws *websocket.Conn
+ GuildId uint64
+ TicketId int
+ }
+
+ WsEvent struct {
+ Type string
+ Data json.RawMessage
+ }
+
+ AuthEvent struct {
+ GuildId uint64 `json:"guild_id,string"`
+ TicketId int `json:"ticket_id"`
+ Token string `json:"token"`
+ }
+)
+
+var timeout = time.Second * 60
+
+func WebChatWs(ctx *gin.Context) {
+ conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ socket := &Socket{
+ Ws: conn,
+ }
+
+ SocketsLock.Lock()
+ Sockets = append(Sockets, socket)
+ SocketsLock.Unlock()
+
+ conn.SetCloseHandler(func(code int, text string) error {
+ i := -1
+ SocketsLock.Lock()
+ defer SocketsLock.Unlock()
+
+ for index, element := range Sockets {
+ if element == socket {
+ i = index
+ break
+ }
+ }
+
+ if i != -1 {
+ Sockets = Sockets[:i+copy(Sockets[i:], Sockets[i+1:])]
+ }
+
+ return nil
+ })
+
+ lastResponse := time.Now()
+ conn.SetPongHandler(func(a string) error {
+ lastResponse = time.Now()
+ return nil
+ })
+
+ go func() {
+ // We can let this func call the CloseHandler
+ for {
+ err := conn.WriteMessage(websocket.PingMessage, []byte("keepalive"))
+ if err != nil {
+ fmt.Println(err.Error())
+ conn.Close()
+ conn.CloseHandler()(1000, "")
+ return
+ }
+
+ time.Sleep(timeout / 2)
+ if time.Since(lastResponse) > timeout {
+ conn.Close()
+ conn.CloseHandler()(1000, "")
+ return
+ }
+ }
+ }()
+
+ for {
+ var event WsEvent
+ err := conn.ReadJSON(&event)
+ if err != nil {
+ break
+ }
+
+ if socket.GuildId == 0 && event.Type != "auth" {
+ conn.Close()
+ break
+ } else if event.Type == "auth" {
+ var authData AuthEvent
+ if err := json.Unmarshal(event.Data, &authData); err != nil {
+ conn.Close()
+ return
+ }
+
+ token, err := jwt.Parse(authData.Token, func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+ }
+
+ return []byte(config.Conf.Server.Secret), nil
+ })
+
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ conn.Close()
+ return
+ }
+
+ userIdStr, ok := claims["userid"].(string)
+ if !ok {
+ conn.Close()
+ return
+ }
+
+ userId, err := strconv.ParseUint(userIdStr, 10, 64)
+ if err != nil {
+ conn.Close()
+ return
+ }
+
+ // Verify the user has permissions to be here
+ permLevel, err := utils.GetPermissionLevel(authData.GuildId, userId)
+ if err != nil {
+ fmt.Println(err.Error())
+ conn.Close()
+ return
+ }
+
+ if permLevel < permission.Admin {
+ fmt.Println(3)
+ conn.Close()
+ return
+ }
+
+ botContext, err := botcontext.ContextForGuild(authData.GuildId)
+ if err != nil {
+ ctx.AbortWithStatusJSON(500, gin.H{
+ "success": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ // Verify the guild is premium
+ premiumTier := rpc.PremiumClient.GetTierByGuildId(authData.GuildId, true, botContext.Token, botContext.RateLimiter)
+ if premiumTier == premium.None {
+ fmt.Println(4)
+ conn.Close()
+ return
+ }
+
+ SocketsLock.Lock()
+ socket.GuildId = authData.GuildId
+ socket.TicketId = authData.TicketId
+ SocketsLock.Unlock()
+ }
+ }
+}
diff --git a/app/http/endpoints/root/whitelabel.go b/app/http/endpoints/root/whitelabel.go
deleted file mode 100644
index e6b8974..0000000
--- a/app/http/endpoints/root/whitelabel.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package root
-
-import (
- "fmt"
- "github.com/TicketsBot/GoPanel/config"
- "github.com/TicketsBot/GoPanel/rpc"
- "github.com/TicketsBot/common/premium"
- "github.com/gin-gonic/contrib/sessions"
- "github.com/gin-gonic/gin"
-)
-
-func WhitelabelHandler(ctx *gin.Context) {
- store := sessions.Default(ctx)
- if store == nil {
- return
- }
- defer store.Save()
-
- userId := store.Get("userid").(uint64)
-
- premiumTier := rpc.PremiumClient.GetTierByUser(userId, false)
- if premiumTier < premium.Whitelabel {
- var isForced bool
- for _, forced := range config.Conf.ForceWhitelabel {
- if forced == userId {
- isForced = true
- break
- }
- }
-
- if !isForced {
- ctx.Redirect(302, fmt.Sprintf("%s/premium", config.Conf.Server.MainSite))
- return
- }
- }
-
- ctx.HTML(200, "main/whitelabel", gin.H{
- "name": store.Get("name").(string),
- "baseurl": config.Conf.Server.BaseUrl,
- "avatar": store.Get("avatar").(string),
- "referralShow": config.Conf.Referral.Show,
- "referralLink": config.Conf.Referral.Link,
- })
-}
diff --git a/app/http/middleware/authenticatetoken.go b/app/http/middleware/authenticatetoken.go
index 8438850..bcb7db3 100644
--- a/app/http/middleware/authenticatetoken.go
+++ b/app/http/middleware/authenticatetoken.go
@@ -38,6 +38,10 @@ func AuthenticateToken(ctx *gin.Context) {
return
}
+ if ctx.Keys == nil {
+ ctx.Keys = make(map[string]interface{})
+ }
+
ctx.Keys["userid"] = parsedId
} else {
ctx.AbortWithStatusJSON(401, utils.ErrorStr("Token is invalid"))
diff --git a/app/http/middleware/cors.go b/app/http/middleware/cors.go
new file mode 100644
index 0000000..98e518c
--- /dev/null
+++ b/app/http/middleware/cors.go
@@ -0,0 +1,25 @@
+package middleware
+
+import (
+ "github.com/TicketsBot/GoPanel/config"
+ "github.com/gin-gonic/gin"
+ "net/http"
+ "strings"
+)
+
+func Cors(config config.Config) func(*gin.Context) {
+ methods := []string{http.MethodOptions, http.MethodGet, http.MethodPost, http.MethodPatch, http.MethodPut, http.MethodDelete}
+ headers := []string{"x-tickets", "Content-Type", "Authorization"}
+
+ return func(ctx *gin.Context) {
+ ctx.Header("Access-Control-Allow-Origin", config.Server.BaseUrl)
+ ctx.Header("Access-Control-Allow-Methods", strings.Join(methods, ", "))
+ ctx.Header("Access-Control-Allow-Headers", strings.Join(headers, ", "))
+ ctx.Header("Access-Control-Allow-Credentials", "true")
+ ctx.Header("Access-Control-Max-Age", "600")
+
+ if ctx.Request.Method == http.MethodOptions {
+ ctx.AbortWithStatus(http.StatusNoContent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/http/server.go b/app/http/server.go
index d76a788..5e43de3 100644
--- a/app/http/server.go
+++ b/app/http/server.go
@@ -1,25 +1,23 @@
package http
import (
- "fmt"
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
api_autoclose "github.com/TicketsBot/GoPanel/app/http/endpoints/api/autoclose"
api_blacklist "github.com/TicketsBot/GoPanel/app/http/endpoints/api/blacklist"
- api_logs "github.com/TicketsBot/GoPanel/app/http/endpoints/api/logs"
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings"
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"
+ api_transcripts "github.com/TicketsBot/GoPanel/app/http/endpoints/api/transcripts"
api_whitelabel "github.com/TicketsBot/GoPanel/app/http/endpoints/api/whitelabel"
- "github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
"github.com/TicketsBot/GoPanel/app/http/endpoints/root"
"github.com/TicketsBot/GoPanel/app/http/middleware"
+ "github.com/TicketsBot/GoPanel/app/http/session"
"github.com/TicketsBot/GoPanel/config"
+ "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/common/permission"
- "github.com/gin-contrib/multitemplate"
"github.com/gin-contrib/static"
- "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/ulule/limiter/v3"
mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
@@ -34,15 +32,7 @@ func StartServer() {
router := gin.Default()
// Sessions
- store, err := sessions.NewRedisStore(
- config.Conf.Server.Session.Threads,
- "tcp", fmt.Sprintf("%s:%d", config.Conf.Redis.Host, config.Conf.Redis.Port),
- config.Conf.Redis.Password,
- []byte(config.Conf.Server.Session.Secret))
- if err != nil {
- panic(err)
- }
- router.Use(sessions.Sessions("panel", store))
+ session.Store = session.NewRedisStore()
// Handle static asset requests
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
@@ -50,39 +40,18 @@ func StartServer() {
router.Use(gin.Recovery())
router.Use(createLimiter(600, time.Minute*10))
- // Register templates
- router.HTMLRender = createRenderer()
+ router.Use(middleware.Cors(config.Conf))
- router.GET("/login", root.LoginHandler)
- router.GET("/callback", root.CallbackHandler)
+ router.GET("/webchat", root.WebChatWs)
- router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect
-
- authorized := router.Group("/", middleware.AuthenticateCookie)
- {
- authorized.POST("/token", createLimiter(2, 10 * time.Second), middleware.VerifyXTicketsHeader, api.TokenHandler)
-
- authenticateGuildAdmin := authorized.Group("/", middleware.AuthenticateGuild(false, permission.Admin))
- authenticateGuildSupport := authorized.Group("/", middleware.AuthenticateGuild(false, permission.Support))
-
- authorized.GET("/", root.IndexHandler)
- authorized.GET("/whitelabel", root.WhitelabelHandler)
- authorized.GET("/logout", root.LogoutHandler)
-
- authenticateGuildAdmin.GET("/manage/:id/settings", manage.SettingsHandler)
- authenticateGuildSupport.GET("/manage/:id/logs", manage.LogsHandler)
- authenticateGuildSupport.GET("/manage/:id/blacklist", manage.BlacklistHandler)
- authenticateGuildAdmin.GET("/manage/:id/panels", manage.PanelHandler)
- authenticateGuildSupport.GET("/manage/:id/tags", manage.TagsHandler)
- authenticateGuildSupport.GET("/manage/:id/teams", serveTemplate("manage/teams"))
-
- authenticateGuildSupport.GET("/manage/:id/tickets", manage.TicketListHandler)
- authenticateGuildSupport.GET("/manage/:id/tickets/view/:ticketId", manage.TicketViewHandler)
-
- authorized.GET("/webchat", manage.WebChatWs)
- }
+ router.POST("/callback", middleware.VerifyXTicketsHeader, root.CallbackHandler)
+ router.POST("/logout", middleware.VerifyXTicketsHeader, middleware.AuthenticateToken, root.LogoutHandler)
apiGroup := router.Group("/api", middleware.VerifyXTicketsHeader, middleware.AuthenticateToken)
+ {
+ apiGroup.GET("/session", api.SessionHandler)
+ }
+
guildAuthApiAdmin := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Admin))
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(true, permission.Support))
{
@@ -90,18 +59,18 @@ func StartServer() {
guildAuthApiSupport.GET("/premium", api.PremiumHandler)
guildAuthApiSupport.GET("/user/:user", api.UserHandler)
guildAuthApiSupport.GET("/roles", api.RolesHandler)
- guildAuthApiSupport.GET("/members/search", createLimiter(10, time.Second * 30), createLimiter(75, time.Minute * 30), api.SearchMembers)
+ guildAuthApiSupport.GET("/members/search", createLimiter(5, time.Second), createLimiter(10, time.Second * 30), createLimiter(75, time.Minute * 30), api.SearchMembers)
guildAuthApiAdmin.GET("/settings", api_settings.GetSettingsHandler)
guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler)
guildAuthApiSupport.GET("/blacklist", api_blacklist.GetBlacklistHandler)
- guildAuthApiSupport.PUT("/blacklist", api_blacklist.AddBlacklistHandler)
+ guildAuthApiSupport.POST("/blacklist/:user", api_blacklist.AddBlacklistHandler)
guildAuthApiSupport.DELETE("/blacklist/:user", api_blacklist.RemoveBlacklistHandler)
guildAuthApiAdmin.GET("/panels", api_panels.ListPanels)
- guildAuthApiAdmin.PUT("/panels", api_panels.CreatePanel)
- guildAuthApiAdmin.PUT("/panels/:panelid", api_panels.UpdatePanel)
+ guildAuthApiAdmin.POST("/panels", api_panels.CreatePanel)
+ guildAuthApiAdmin.PATCH("/panels/:panelid", api_panels.UpdatePanel)
guildAuthApiAdmin.DELETE("/panels/:panelid", api_panels.DeletePanel)
guildAuthApiAdmin.GET("/multipanels", api_panels.MultiPanelList)
@@ -109,7 +78,8 @@ func StartServer() {
guildAuthApiAdmin.PATCH("/multipanels/:panelid", api_panels.MultiPanelUpdate)
guildAuthApiAdmin.DELETE("/multipanels/:panelid", api_panels.MultiPanelDelete)
- guildAuthApiSupport.GET("/logs/", api_logs.GetLogs)
+ guildAuthApiSupport.GET("/transcripts", createLimiter(5, 5 * time.Second), createLimiter(20, time.Minute), api_transcripts.ListTranscripts)
+ guildAuthApiSupport.GET("/transcripts/:ticketId", createLimiter(10, 10 * time.Second), api_transcripts.GetTranscriptHandler)
guildAuthApiSupport.GET("/tickets", api_ticket.GetTickets)
guildAuthApiSupport.GET("/tickets/:ticketId", api_ticket.GetTicket)
@@ -127,7 +97,7 @@ func StartServer() {
guildAuthApiAdmin.POST("/autoclose", api_autoclose.PostAutoClose)
guildAuthApiAdmin.GET("/team", api_team.GetTeams)
- guildAuthApiAdmin.GET("/team/:teamid", createLimiter(5, time.Second * 15), api_team.GetMembers)
+ guildAuthApiAdmin.GET("/team/:teamid", createLimiter(10, time.Second * 30), api_team.GetMembers)
guildAuthApiAdmin.POST("/team", createLimiter(10, time.Minute), api_team.CreateTeam)
guildAuthApiAdmin.PUT("/team/:teamid/:snowflake", createLimiter(5, time.Second * 10), api_team.AddMember)
guildAuthApiAdmin.DELETE("/team/:teamid", api_team.DeleteTeam)
@@ -141,18 +111,17 @@ func StartServer() {
userGroup.GET("/permissionlevel", api.GetPermissionLevel)
{
- whitelabelGroup := userGroup.Group("/whitelabel", middleware.VerifyWhitelabel(false))
- whitelabelApiGroup := userGroup.Group("/whitelabel", middleware.VerifyWhitelabel(true))
+ whitelabelGroup := userGroup.Group("/whitelabel", middleware.VerifyWhitelabel(true))
whitelabelGroup.GET("/", api_whitelabel.WhitelabelGet)
- whitelabelApiGroup.GET("/errors", api_whitelabel.WhitelabelGetErrors)
- whitelabelApiGroup.GET("/guilds", api_whitelabel.WhitelabelGetGuilds)
- whitelabelApiGroup.GET("/public-key", api_whitelabel.WhitelabelGetPublicKey)
- whitelabelApiGroup.POST("/public-key", api_whitelabel.WhitelabelPostPublicKey)
- whitelabelApiGroup.POST("/create-interactions", api_whitelabel.GetWhitelabelCreateInteractions())
+ whitelabelGroup.GET("/errors", api_whitelabel.WhitelabelGetErrors)
+ whitelabelGroup.GET("/guilds", api_whitelabel.WhitelabelGetGuilds)
+ whitelabelGroup.GET("/public-key", api_whitelabel.WhitelabelGetPublicKey)
+ whitelabelGroup.POST("/public-key", api_whitelabel.WhitelabelPostPublicKey)
+ whitelabelGroup.POST("/create-interactions", api_whitelabel.GetWhitelabelCreateInteractions())
- whitelabelApiGroup.POST("/", createLimiter(10, time.Minute), api_whitelabel.WhitelabelPost)
- whitelabelApiGroup.POST("/status", createLimiter(1, time.Second*5), api_whitelabel.WhitelabelStatusPost)
+ whitelabelGroup.POST("/", createLimiter(10, time.Minute), api_whitelabel.WhitelabelPost)
+ whitelabelGroup.POST("/status", createLimiter(1, time.Second*5), api_whitelabel.WhitelabelStatusPost)
}
}
@@ -163,82 +132,32 @@ func StartServer() {
func serveTemplate(templateName string) func(*gin.Context) {
return func(ctx *gin.Context) {
- store := sessions.Default(ctx)
guildId := ctx.Keys["guildid"].(uint64)
+ userId := ctx.Keys["userid"].(uint64)
+
+ store, err := session.Store.Get(userId)
+ if err != nil {
+ if err == session.ErrNoSession {
+ ctx.JSON(401, gin.H{
+ "success": false,
+ "auth": true,
+ })
+ } else {
+ ctx.JSON(500, utils.ErrorJson(err))
+ }
+
+ return
+ }
ctx.HTML(200, templateName, gin.H{
- "name": store.Get("name").(string),
+ "name": store.Name,
"guildId": guildId,
- "avatar": store.Get("avatar").(string),
+ "avatar": store.Avatar,
"baseUrl": config.Conf.Server.BaseUrl,
})
}
}
-func createRenderer() multitemplate.Renderer {
- r := multitemplate.NewRenderer()
-
- r = addMainTemplate(r, "index")
- r = addMainTemplate(r, "whitelabel")
-
- r = addManageTemplate(r, "blacklist")
- r = addManageTemplate(r, "logs")
- r = addManageTemplate(r, "modmaillogs")
- r = addManageTemplate(r, "settings", "./public/templates/includes/substitutionmodal.tmpl")
- r = addManageTemplate(r, "ticketlist")
- r = addManageTemplate(r, "ticketview")
- r = addManageTemplate(r, "panels", "./public/templates/includes/substitutionmodal.tmpl", "./public/templates/includes/paneleditmodal.tmpl", "./public/templates/includes/multipaneleditmodal.tmpl")
- r = addManageTemplate(r, "tags")
- r = addManageTemplate(r, "teams")
-
- r = addErrorTemplate(r)
-
- return r
-}
-
-func addMainTemplate(renderer multitemplate.Renderer, name string, extra ...string) multitemplate.Renderer {
- files := []string{
- "./public/templates/layouts/main.tmpl",
- "./public/templates/includes/head.tmpl",
- "./public/templates/includes/sidebar.tmpl",
- "./public/templates/includes/loadingscreen.tmpl",
- "./public/templates/includes/notifymodal.tmpl",
- fmt.Sprintf("./public/templates/views/%s.tmpl", name),
- }
-
- files = append(files, extra...)
-
- renderer.AddFromFiles(fmt.Sprintf("main/%s", name), files...)
- return renderer
-}
-
-func addManageTemplate(renderer multitemplate.Renderer, name string, extra ...string) multitemplate.Renderer {
- files := []string{
- "./public/templates/layouts/manage.tmpl",
- "./public/templates/includes/head.tmpl",
- "./public/templates/includes/sidebar.tmpl",
- "./public/templates/includes/navbar.tmpl",
- "./public/templates/includes/loadingscreen.tmpl",
- "./public/templates/includes/notifymodal.tmpl",
- fmt.Sprintf("./public/templates/views/%s.tmpl", name),
- }
-
- files = append(files, extra...)
-
- renderer.AddFromFiles(fmt.Sprintf("manage/%s", name), files...)
- return renderer
-}
-
-func addErrorTemplate(renderer multitemplate.Renderer) multitemplate.Renderer {
- files := []string{
- "./public/templates/layouts/error.tmpl",
- "./public/templates/includes/head.tmpl",
- }
-
- renderer.AddFromFiles("error", files...)
- return renderer
-}
-
func createLimiter(limit int64, period time.Duration) func(*gin.Context) {
store := memory.NewStore()
rate := limiter.Rate{
diff --git a/app/http/session/redisstore.go b/app/http/session/redisstore.go
new file mode 100644
index 0000000..ecca6d1
--- /dev/null
+++ b/app/http/session/redisstore.go
@@ -0,0 +1,54 @@
+package session
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/TicketsBot/GoPanel/messagequeue"
+ "github.com/go-redis/redis"
+)
+
+var ErrNoSession = errors.New("no session data found")
+
+type RedisStore struct {
+ client *redis.Client
+}
+
+func NewRedisStore() *RedisStore {
+ return &RedisStore{
+ client: messagequeue.Client.Client,
+ }
+}
+
+var keyPrefix = "panel:session:"
+
+func (s *RedisStore) Get(userId uint64) (SessionData, error) {
+ raw, err := s.client.Get(fmt.Sprintf("%s:%d", keyPrefix, userId)).Result()
+ if err != nil {
+ if err == redis.Nil {
+ err = ErrNoSession
+ }
+
+ return SessionData{}, err
+ }
+
+ var data SessionData
+ if err := json.Unmarshal([]byte(raw), &data); err != nil {
+ return SessionData{}, err
+ }
+
+ return data, nil
+}
+
+func (s *RedisStore) Set(userId uint64, data SessionData) error {
+ encoded, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+
+ return s.client.Set(fmt.Sprintf("%s:%d", keyPrefix, userId), encoded, 0).Err()
+}
+
+func (s *RedisStore) Clear(userId uint64) error {
+ return s.client.Del(fmt.Sprintf("%s:%d", keyPrefix, userId)).Err()
+}
\ No newline at end of file
diff --git a/app/http/session/sessiondata.go b/app/http/session/sessiondata.go
new file mode 100644
index 0000000..5b074ff
--- /dev/null
+++ b/app/http/session/sessiondata.go
@@ -0,0 +1,10 @@
+package session
+
+type SessionData struct {
+ AccessToken string `json:"access_token"`
+ Expiry int64 `json:"expiry"`
+ RefreshToken string `json:"refresh_token"`
+ Name string `json:"name"`
+ Avatar string `json:"avatar_hash"`
+ HasGuilds bool `json:"has_guilds"`
+}
diff --git a/app/http/session/store.go b/app/http/session/store.go
new file mode 100644
index 0000000..71a4862
--- /dev/null
+++ b/app/http/session/store.go
@@ -0,0 +1,9 @@
+package session
+
+type SessionStore interface {
+ Get(userId uint64) (SessionData, error)
+ Set(userId uint64, data SessionData) error
+ Clear(userId uint64) error
+}
+
+var Store SessionStore
\ No newline at end of file
diff --git a/botcontext/botcontext.go b/botcontext/botcontext.go
index 6745e45..b5d86c0 100644
--- a/botcontext/botcontext.go
+++ b/botcontext/botcontext.go
@@ -112,3 +112,17 @@ func (ctx BotContext) SearchMembers(guildId uint64, query string) (members []mem
return
}
+
+
+func (ctx BotContext) ListMembers(guildId uint64) (members []member.Member, err error) {
+ data := rest.ListGuildMembersData{
+ Limit: 100,
+ }
+
+ members, err = rest.ListGuildMembers(ctx.Token, ctx.RateLimiter, guildId, data)
+ if err == nil {
+ go cache.Instance.StoreMembers(members, guildId)
+ }
+
+ return
+}
diff --git a/cmd/panel/main.go b/cmd/panel/main.go
index 681a5c7..1cbe81d 100644
--- a/cmd/panel/main.go
+++ b/cmd/panel/main.go
@@ -5,7 +5,7 @@ import (
"encoding/binary"
"fmt"
"github.com/TicketsBot/GoPanel/app/http"
- "github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
+ "github.com/TicketsBot/GoPanel/app/http/endpoints/root"
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/messagequeue"
@@ -13,6 +13,7 @@ import (
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/archiverclient"
+ "github.com/TicketsBot/common/chatrelay"
"github.com/TicketsBot/common/premium"
"github.com/TicketsBot/worker/bot/i18n"
"github.com/apex/log"
@@ -36,7 +37,7 @@ func main() {
database.ConnectToDatabase()
cache.Instance = cache.NewCache()
- manage.Archiver = archiverclient.NewArchiverClientWithTimeout(config.Conf.Bot.ObjectStore, time.Second*15, []byte(config.Conf.Bot.AesKey))
+ utils.ArchiverClient = archiverclient.NewArchiverClientWithTimeout(config.Conf.Bot.ObjectStore, time.Second*15, []byte(config.Conf.Bot.AesKey))
utils.LoadEmoji()
if err := i18n.LoadMessages(database.Client); err != nil {
@@ -48,7 +49,7 @@ func main() {
}
messagequeue.Client = messagequeue.NewRedisClient()
- go Listen(messagequeue.Client)
+ go ListenChat(messagequeue.Client)
rpc.PremiumClient = premium.NewPremiumLookupClient(
premium.NewPatreonClient(config.Conf.Bot.PremiumLookupProxyUrl, config.Conf.Bot.PremiumLookupProxyKey),
@@ -60,19 +61,19 @@ func main() {
http.StartServer()
}
-func Listen(client messagequeue.RedisClient) {
- ch := make(chan messagequeue.TicketMessage)
- go client.ListenForMessages(ch)
+func ListenChat(client messagequeue.RedisClient) {
+ ch := make(chan chatrelay.MessageData)
+ go chatrelay.Listen(client.Client, ch)
- for decoded := range ch {
- manage.SocketsLock.Lock()
- for _, socket := range manage.Sockets {
- if socket.Guild == decoded.GuildId && socket.Ticket == decoded.TicketId {
- if err := socket.Ws.WriteJSON(decoded); err != nil {
+ for event := range ch {
+ root.SocketsLock.RLock()
+ for _, socket := range root.Sockets {
+ if socket.GuildId == event.Ticket.GuildId && socket.TicketId == event.Ticket.Id {
+ if err := socket.Ws.WriteJSON(event.Message); err != nil {
fmt.Println(err.Error())
}
}
}
- manage.SocketsLock.Unlock()
+ root.SocketsLock.RUnlock()
}
}
diff --git a/config/config.go b/config/config.go
index faf3554..f6f99f5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -18,7 +18,6 @@ type (
Bot Bot
Redis Redis
Cache Cache
- Referral Referral
}
Server struct {
@@ -119,7 +118,6 @@ func fromEnvvar() {
oauthId, _ := strconv.ParseUint(os.Getenv("OAUTH_ID"), 10, 64)
redisPort, _ := strconv.Atoi(os.Getenv("REDIS_PORT"))
redisThreads, _ := strconv.Atoi(os.Getenv("REDIS_THREADS"))
- showReferral, _ := strconv.ParseBool(os.Getenv("REFERRAL_SHOW"))
Conf = Config{
Admins: admins,
@@ -163,9 +161,5 @@ func fromEnvvar() {
Cache: Cache{
Uri: os.Getenv("CACHE_URI"),
},
- Referral: Referral{
- Show: showReferral,
- Link: os.Getenv("REFERRAL_LINK"),
- },
}
}
diff --git a/envvars.md b/envvars.md
index d77a239..15fdcaf 100644
--- a/envvars.md
+++ b/envvars.md
@@ -1,3 +1,13 @@
+# Build
+---
+- CLIENT_ID
+- REDIRECT_URI
+- API_URL
+- WS_URL
+
+# Runtime
+---
+
- ADMINS
- FORCED_WHITELABEL
- SERVER_ADDR
@@ -22,5 +32,3 @@
- REDIS_PASSWORD
- REDIS_THREADS
- CACHE_URI
-- REFERRAL_SHOW
-- REFERRAL_LINK
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..b39a19d
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/public/build
+
+.DS_Store
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..7a43b91
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,876 @@
+{
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
+ "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
+ "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.14.0",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@fortawesome/fontawesome-common-types": {
+ "version": "0.2.35",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
+ "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw=="
+ },
+ "@fortawesome/free-regular-svg-icons": {
+ "version": "5.15.3",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz",
+ "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.35"
+ }
+ },
+ "@fortawesome/free-solid-svg-icons": {
+ "version": "5.15.3",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz",
+ "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.35"
+ }
+ },
+ "@polka/url": {
+ "version": "1.0.0-next.15",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
+ "integrity": "sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA=="
+ },
+ "@rollup/plugin-commonjs": {
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz",
+ "integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.1",
+ "glob": "^7.1.6",
+ "is-reference": "^1.2.1",
+ "magic-string": "^0.25.7",
+ "resolve": "^1.17.0"
+ }
+ },
+ "@rollup/plugin-node-resolve": {
+ "version": "11.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
+ "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "builtin-modules": "^3.1.0",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.19.0"
+ }
+ },
+ "@rollup/plugin-replace": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz",
+ "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "magic-string": "^0.25.7"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "dependencies": {
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ }
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "15.6.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
+ "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==",
+ "dev": true
+ },
+ "@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+ "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.1",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.5.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "console-clear": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
+ "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ=="
+ },
+ "deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "fa-svelte": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fa-svelte/-/fa-svelte-3.1.0.tgz",
+ "integrity": "sha512-RqBOWwt7sc+ta9GFjbu5GOwKFRzn3rMPPSqvSGpIwsfVnpMjiI5ttv84lwNsCMEYI6/lu/iH21HUcE3TLz8RGQ=="
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
+ "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "get-port": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+ "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
+ },
+ "glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-core-module": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
+ "jest-worker": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
+ "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
+ },
+ "livereload": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
+ "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.5.0",
+ "livereload-js": "^3.3.1",
+ "opts": ">= 1.2.0",
+ "ws": "^7.4.3"
+ }
+ },
+ "livereload-js": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.2.tgz",
+ "integrity": "sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA==",
+ "dev": true
+ },
+ "local-access": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz",
+ "integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw=="
+ },
+ "magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "mime": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
+ "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "mri": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
+ "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ=="
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "opts": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz",
+ "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true
+ },
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+ "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "require-relative": {
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
+ "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
+ },
+ "rollup": {
+ "version": "2.50.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.50.4.tgz",
+ "integrity": "sha512-mBQa9O6bdqur7a6R+TXcbdYgfO2arXlDG+rSrWfwAvsiumpJjD4OS23R9QuhItuz8ysWb8mZ91CFFDQUhJY+8Q==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.1"
+ }
+ },
+ "rollup-plugin-css-only": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-3.1.0.tgz",
+ "integrity": "sha512-TYMOE5uoD76vpj+RTkQLzC9cQtbnJNktHPB507FzRWBVaofg7KhIqq1kGbcVOadARSozWF883Ho9KpSPKH8gqA==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "4"
+ },
+ "dependencies": {
+ "@rollup/pluginutils": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
+ "integrity": "sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ }
+ }
+ }
+ },
+ "rollup-plugin-livereload": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.0.tgz",
+ "integrity": "sha512-oC/8NqumGYuphkqrfszOHUUIwzKsaHBICw6QRwT5uD07gvePTS+HW+GFwu6f9K8W02CUuTvtIM9AWJrbj4wE1A==",
+ "dev": true,
+ "requires": {
+ "livereload": "^0.9.1"
+ }
+ },
+ "rollup-plugin-svelte": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz",
+ "integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==",
+ "dev": true,
+ "requires": {
+ "require-relative": "^0.8.7",
+ "rollup-pluginutils": "^2.8.2"
+ }
+ },
+ "rollup-plugin-terser": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
+ "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "jest-worker": "^26.2.1",
+ "serialize-javascript": "^4.0.0",
+ "terser": "^5.0.0"
+ }
+ },
+ "rollup-pluginutils": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
+ "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^0.6.1"
+ },
+ "dependencies": {
+ "estree-walker": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
+ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
+ "dev": true
+ }
+ }
+ },
+ "sade": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
+ "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
+ "requires": {
+ "mri": "^1.1.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "semiver": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
+ "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg=="
+ },
+ "serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "sirv": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.12.tgz",
+ "integrity": "sha512-+jQoCxndz7L2tqQL4ZyzfDhky0W/4ZJip3XoOuxyQWnAwMxindLl3Xv1qT4x1YX/re0leShvTm8Uk0kQspGhBg==",
+ "requires": {
+ "@polka/url": "^1.0.0-next.15",
+ "mime": "^2.3.1",
+ "totalist": "^1.0.0"
+ }
+ },
+ "sirv-cli": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-1.0.12.tgz",
+ "integrity": "sha512-Rs5PvF3a48zuLmrl8vcqVv9xF/WWPES19QawVkpdzqx7vD5SMZS07+ece1gK4umbslXN43YeIksYtQM5csgIzQ==",
+ "requires": {
+ "console-clear": "^1.1.0",
+ "get-port": "^3.2.0",
+ "kleur": "^3.0.0",
+ "local-access": "^1.0.1",
+ "sade": "^1.6.0",
+ "semiver": "^1.0.0",
+ "sirv": "^1.0.12",
+ "tinydate": "^1.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "svelte": {
+ "version": "3.38.2",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.38.2.tgz",
+ "integrity": "sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==",
+ "dev": true
+ },
+ "svelte-click-outside": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/svelte-click-outside/-/svelte-click-outside-1.0.0.tgz",
+ "integrity": "sha512-TVDn5Vd8L0WI0Y9BFh/2I7judkIqYCbFKkGwGl/f8D0inwBFNyU0weKhrbJY4VQtYnWriq0NPl+mIYGisgALbw=="
+ },
+ "svelte-emoji-selector": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/svelte-emoji-selector/-/svelte-emoji-selector-1.0.1.tgz",
+ "integrity": "sha512-gGjDydt+79YQIdUyz/r1sHSkjLko2rb9qHNiBveC5RSl6rJ0mob4T5DrADRArjQ/HA8kNfEJFyqbnLoA+dyLqA==",
+ "requires": {
+ "@fortawesome/free-regular-svg-icons": "^5.10.1",
+ "@fortawesome/free-solid-svg-icons": "^5.10.1",
+ "fa-svelte": "^3.0.0",
+ "popper.js": "^1.15.0",
+ "svelte-click-outside": "^1.0.0",
+ "svelte-tabs": "^1.1.0"
+ }
+ },
+ "svelte-router-spa": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/svelte-router-spa/-/svelte-router-spa-6.0.2.tgz",
+ "integrity": "sha512-ySs/2TnjdLnvo0tHfdJsRPhPl0Mj4/h2qi0Zb8t4zC+BBBaCr6cZc7MtRfgzD4IMp80Nqe7ZXd/hCJuHSGtf5A==",
+ "requires": {
+ "url-params-parser": "^1.0.3"
+ }
+ },
+ "svelte-select": {
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-3.17.0.tgz",
+ "integrity": "sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA=="
+ },
+ "svelte-tabs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/svelte-tabs/-/svelte-tabs-1.1.0.tgz",
+ "integrity": "sha512-bCynxgET2uvqpB6xf/dVyqHjzmumRURQyh2QqXlrki8NxzO7h2WghF8qgpb5qeB5NTX1bMU+9Q5Hf5ey2WLaMg=="
+ },
+ "terser": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",
+ "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.7.2",
+ "source-map-support": "~0.5.19"
+ }
+ },
+ "tinydate": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz",
+ "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w=="
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "totalist": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
+ "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
+ },
+ "url-params-parser": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/url-params-parser/-/url-params-parser-1.0.4.tgz",
+ "integrity": "sha512-0m6BqGpY2OetTZ3UPTLKkbTfUHigsX2YhrzORT9iYiyUJ/SP2WJ3cggg2YWtvMs36GPwK9Q44ffddyarniu2Tg=="
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "ws": {
+ "version": "7.4.6",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+ "dev": true
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..6447598
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "build": "rollup -c",
+ "dev": "rollup -c -w",
+ "start": "sirv public -s --no-clear --host 0.0.0.0"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^17.0.0",
+ "@rollup/plugin-node-resolve": "^11.0.0",
+ "@rollup/plugin-replace": "^2.4.2",
+ "rollup": "^2.3.4",
+ "rollup-plugin-css-only": "^3.1.0",
+ "rollup-plugin-livereload": "^2.0.0",
+ "rollup-plugin-svelte": "^7.0.0",
+ "rollup-plugin-terser": "^7.0.0",
+ "svelte": "^3.0.0"
+ },
+ "dependencies": {
+ "axios": "^0.21.1",
+ "sirv-cli": "^1.0.0",
+ "svelte-emoji-selector": "^1.0.1",
+ "svelte-router-spa": "^6.0.2",
+ "svelte-select": "^3.17.0"
+ }
+}
diff --git a/public/static/img/custom.png b/frontend/public/assets/img/custom.png
similarity index 100%
rename from public/static/img/custom.png
rename to frontend/public/assets/img/custom.png
diff --git a/public/static/img/favicon.ico b/frontend/public/assets/img/favicon.ico
similarity index 100%
rename from public/static/img/favicon.ico
rename to frontend/public/assets/img/favicon.ico
diff --git a/public/static/img/loading-bubbles.svg b/frontend/public/assets/img/loading-bubbles.svg
similarity index 100%
rename from public/static/img/loading-bubbles.svg
rename to frontend/public/assets/img/loading-bubbles.svg
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 0000000..841fcb2
Binary files /dev/null and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/global.css b/frontend/public/global.css
new file mode 100644
index 0000000..395ad58
--- /dev/null
+++ b/frontend/public/global.css
@@ -0,0 +1,64 @@
+@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap');
+
+html, body {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+body, h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6, p, .navbar, .brand, .btn-simple, .alert, a, .td-name, td, button.close {
+ font-family: 'Noto Sans', sans-serif !important;
+ font-weight: 400 !important;
+}
+
+h1, h2, h3, h4, h5, h6, p, span {
+ margin: 0;
+}
+
+body {
+ line-height: 1.5;
+ font-size: 1rem;
+ font-weight: 400;
+ background-color: #121212 !important;
+ color: white;
+ margin: 0;
+ padding: 0 !important;
+ box-sizing: border-box;
+ /*font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;*/
+}
+
+label {
+ display: block;
+}
+
+input, button, select, textarea {
+ font-family: inherit;
+ font-size: inherit;
+ -webkit-padding: 0.4em 0;
+ padding: 0.4em;
+ box-sizing: border-box;
+ border: 1px solid #ccc;
+ border-radius: 2px;
+}
+
+input:disabled {
+ color: #ccc;
+}
+
+button {
+ color: #333;
+ background-color: #f4f4f4;
+ outline: none;
+}
+
+button:disabled {
+ color: #999;
+}
+
+button:not(:disabled):active {
+ background-color: #ddd;
+}
+
+button:focus {
+ border-color: #666;
+}
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000..8239834
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Tickets Dashboard
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
new file mode 100644
index 0000000..4f11d02
--- /dev/null
+++ b/frontend/rollup.config.js
@@ -0,0 +1,86 @@
+import svelte from 'rollup-plugin-svelte';
+import commonjs from '@rollup/plugin-commonjs';
+import resolve from '@rollup/plugin-node-resolve';
+import livereload from 'rollup-plugin-livereload';
+import {terser} from 'rollup-plugin-terser';
+import css from 'rollup-plugin-css-only';
+import replace from "@rollup/plugin-replace";
+
+const production = !process.env.ROLLUP_WATCH;
+
+function serve() {
+ let server;
+
+ function toExit() {
+ if (server) server.kill(0);
+ }
+
+ return {
+ writeBundle() {
+ if (server) return;
+ server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
+ stdio: ['ignore', 'inherit', 'inherit'],
+ shell: true
+ });
+
+ process.on('SIGTERM', toExit);
+ process.on('exit', toExit);
+ }
+ };
+}
+
+export default {
+ input: 'src/main.js',
+ output: {
+ sourcemap: true,
+ format: 'iife',
+ name: 'app',
+ file: 'public/build/bundle.js'
+ },
+ plugins: [
+ svelte({
+ compilerOptions: {
+ // enable run-time checks when not in production
+ dev: !production
+ }
+ }),
+ // we'll extract any component CSS out into
+ // a separate file - better for performance
+ css({output: 'bundle.css'}),
+
+ // If you have external dependencies installed from
+ // npm, you'll most likely need these plugins. In
+ // some cases you'll need additional configuration -
+ // consult the documentation for details:
+ // https://github.com/rollup/plugins/tree/master/packages/commonjs
+ resolve({
+ browser: true,
+ dedupe: ['svelte']
+ }),
+ commonjs(),
+
+ replace({
+ env: JSON.stringify({
+ CLIENT_ID: process.env.CLIENT_ID,
+ REDIRECT_URI: process.env.REDIRECT_URI,
+ API_URL: process.env.API_URL,
+ WS_URL: process.env.WS_URL,
+ })
+ }),
+
+ // In dev mode, call `npm run start` once
+ // the bundle has been generated
+ !production && serve(),
+
+ // Watch the `public` directory and refresh the
+ // browser on changes when not in production
+ !production && livereload('public'),
+
+ // If we're building for production (npm run build
+ // instead of npm run dev), minify
+ production && terser()
+ ],
+ watch: {
+ clearScreen: false
+ }
+};
diff --git a/frontend/scripts/setupTypeScript.js b/frontend/scripts/setupTypeScript.js
new file mode 100644
index 0000000..1eb6b77
--- /dev/null
+++ b/frontend/scripts/setupTypeScript.js
@@ -0,0 +1,117 @@
+// @ts-check
+
+/** This script modifies the project to support TS code in .svelte files like:
+
+
+
+ As well as validating the code for CI.
+ */
+
+/** To work on this script:
+ rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
+*/
+
+const fs = require("fs")
+const path = require("path")
+const { argv } = require("process")
+
+const projectRoot = argv[2] || path.join(__dirname, "..")
+
+// Add deps to pkg.json
+const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
+packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
+ "svelte-check": "^1.0.0",
+ "svelte-preprocess": "^4.0.0",
+ "@rollup/plugin-typescript": "^8.0.0",
+ "typescript": "^4.0.0",
+ "tslib": "^2.0.0",
+ "@tsconfig/svelte": "^1.0.0"
+})
+
+// Add script for checking
+packageJSON.scripts = Object.assign(packageJSON.scripts, {
+ "validate": "svelte-check"
+})
+
+// Write the package JSON
+fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
+
+// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
+const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
+const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
+fs.renameSync(beforeMainJSPath, afterMainTSPath)
+
+// Switch the app.svelte file to use TS
+const appSveltePath = path.join(projectRoot, "src", "App.svelte")
+let appFile = fs.readFileSync(appSveltePath, "utf8")
+appFile = appFile.replace("
diff --git a/frontend/src/components/Button.svelte b/frontend/src/components/Button.svelte
new file mode 100644
index 0000000..37bb5ae
--- /dev/null
+++ b/frontend/src/components/Button.svelte
@@ -0,0 +1,68 @@
+
+ {#if icon !== undefined}
+
+ {/if}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Card.svelte b/frontend/src/components/Card.svelte
new file mode 100644
index 0000000..b31ccbb
--- /dev/null
+++ b/frontend/src/components/Card.svelte
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+ No Content :(
+
+
+
+
+ {#if footer}
+
+ {/if}
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/CategoryDropdown.svelte b/frontend/src/components/CategoryDropdown.svelte
new file mode 100644
index 0000000..a81aaa9
--- /dev/null
+++ b/frontend/src/components/CategoryDropdown.svelte
@@ -0,0 +1,22 @@
+
+ {#each channels as channel}
+ {#if channel.type === 4}
+
+ {channel.name}
+
+ {/if}
+ {/each}
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/ChannelDropdown.svelte b/frontend/src/components/ChannelDropdown.svelte
new file mode 100644
index 0000000..f74dafe
--- /dev/null
+++ b/frontend/src/components/ChannelDropdown.svelte
@@ -0,0 +1,22 @@
+
+ {#each channels as channel}
+ {#if channel.type === 0}
+
+ #{channel.name}
+
+ {/if}
+ {/each}
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/DiscordMessages.svelte b/frontend/src/components/DiscordMessages.svelte
new file mode 100644
index 0000000..ed525eb
--- /dev/null
+++ b/frontend/src/components/DiscordMessages.svelte
@@ -0,0 +1,108 @@
+
+
+
+ {#each messages as message}
+
+ {message.author.username}: {message.content}
+
+ {/each}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Guild.svelte b/frontend/src/components/Guild.svelte
new file mode 100644
index 0000000..5b08bbd
--- /dev/null
+++ b/frontend/src/components/Guild.svelte
@@ -0,0 +1,108 @@
+
+
+ {#if guild.icon === undefined || guild.icon === ""}
+
+ {:else}
+
+ {/if}
+
+
+
+
+ {guild.name}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/InviteBadge.svelte b/frontend/src/components/InviteBadge.svelte
new file mode 100644
index 0000000..92e6c43
--- /dev/null
+++ b/frontend/src/components/InviteBadge.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Invite to your server
+
+
+
+
diff --git a/frontend/src/components/NamingScheme.svelte b/frontend/src/components/NamingScheme.svelte
new file mode 100644
index 0000000..de2afb9
--- /dev/null
+++ b/frontend/src/components/NamingScheme.svelte
@@ -0,0 +1,17 @@
+
+
+
+ #ticket-1
+
+
+
+
+ #ticket-ryan
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/NavElement.svelte b/frontend/src/components/NavElement.svelte
new file mode 100644
index 0000000..7b06d1f
--- /dev/null
+++ b/frontend/src/components/NavElement.svelte
@@ -0,0 +1,41 @@
+
+ {#if link}
+
+
+
+
+
+
+ {:else}
+
+
+
+
+
+
+ {/if}
+
+
+
+
+
diff --git a/frontend/src/components/PanelDropdown.svelte b/frontend/src/components/PanelDropdown.svelte
new file mode 100644
index 0000000..4d0a355
--- /dev/null
+++ b/frontend/src/components/PanelDropdown.svelte
@@ -0,0 +1,28 @@
+{label}
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/form/Checkbox.svelte b/frontend/src/components/form/Checkbox.svelte
new file mode 100644
index 0000000..8a19fec
--- /dev/null
+++ b/frontend/src/components/form/Checkbox.svelte
@@ -0,0 +1,21 @@
+
+ {label}
+
+
+
+
+
+
diff --git a/frontend/src/components/form/Colour.svelte b/frontend/src/components/form/Colour.svelte
new file mode 100644
index 0000000..95045f0
--- /dev/null
+++ b/frontend/src/components/form/Colour.svelte
@@ -0,0 +1,20 @@
+
+ {label}
+
+
+
+
+
+
diff --git a/frontend/src/components/form/Dropdown.svelte b/frontend/src/components/form/Dropdown.svelte
new file mode 100644
index 0000000..272749c
--- /dev/null
+++ b/frontend/src/components/form/Dropdown.svelte
@@ -0,0 +1,22 @@
+
+ {label}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/form/EmojiInput.svelte b/frontend/src/components/form/EmojiInput.svelte
new file mode 100644
index 0000000..376bf7f
--- /dev/null
+++ b/frontend/src/components/form/EmojiInput.svelte
@@ -0,0 +1,54 @@
+
+
{label}
+
+
+ {#if !disabled}
+
+ {/if}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/form/Input.svelte b/frontend/src/components/form/Input.svelte
new file mode 100644
index 0000000..7ad5180
--- /dev/null
+++ b/frontend/src/components/form/Input.svelte
@@ -0,0 +1,25 @@
+
+ {#if label !== undefined}
+ {label}
+ {/if}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/form/MultiSelect.svelte b/frontend/src/components/form/MultiSelect.svelte
new file mode 100644
index 0000000..cd5d782
--- /dev/null
+++ b/frontend/src/components/form/MultiSelect.svelte
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+ {selected}
+ {#each selected as value}
+ value
+
+ {/each}
+
+
+
+
+
+
+
+ {#if showOptions}
+
+ {#each filtered as option}
+ {option[1]}
+ {/each}
+
+ {/if}
+
\ No newline at end of file
diff --git a/frontend/src/components/form/Number.svelte b/frontend/src/components/form/Number.svelte
new file mode 100644
index 0000000..9366040
--- /dev/null
+++ b/frontend/src/components/form/Number.svelte
@@ -0,0 +1,32 @@
+
+ {label}
+
+
+
+
diff --git a/frontend/src/components/form/Radio.svelte b/frontend/src/components/form/Radio.svelte
new file mode 100644
index 0000000..081b37a
--- /dev/null
+++ b/frontend/src/components/form/Radio.svelte
@@ -0,0 +1,38 @@
+
+ {label}
+
+
+
+
+
+
diff --git a/frontend/src/components/form/RoleSelect.svelte b/frontend/src/components/form/RoleSelect.svelte
new file mode 100644
index 0000000..a7b8399
--- /dev/null
+++ b/frontend/src/components/form/RoleSelect.svelte
@@ -0,0 +1,31 @@
+{#if label !== undefined}
+ {label}
+{/if}
+
+
+
+
+
+
diff --git a/frontend/src/components/form/Textarea.svelte b/frontend/src/components/form/Textarea.svelte
new file mode 100644
index 0000000..a59daca
--- /dev/null
+++ b/frontend/src/components/form/Textarea.svelte
@@ -0,0 +1,22 @@
+
+ {label}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/form/UserSelect.svelte b/frontend/src/components/form/UserSelect.svelte
new file mode 100644
index 0000000..58cc236
--- /dev/null
+++ b/frontend/src/components/form/UserSelect.svelte
@@ -0,0 +1,45 @@
+{#if label !== undefined}
+ {label}
+{/if}
+
+
+
+
+
+
diff --git a/frontend/src/components/manage/AutoCloseCard.svelte b/frontend/src/components/manage/AutoCloseCard.svelte
new file mode 100644
index 0000000..fcf305a
--- /dev/null
+++ b/frontend/src/components/manage/AutoCloseCard.svelte
@@ -0,0 +1,42 @@
+
+
+ Auto Close
+
+
+
+
+
+
+ This feature is currently disabled. Discord will soon be releasing message threads,
+ which will incorporate their own auto-close behaviour. Thank you for your patience.
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/manage/ClaimsCard.svelte b/frontend/src/components/manage/ClaimsCard.svelte
new file mode 100644
index 0000000..2332b57
--- /dev/null
+++ b/frontend/src/components/manage/ClaimsCard.svelte
@@ -0,0 +1,103 @@
+
+
+ Claim Settings
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/manage/MultiPanelCreationForm.svelte b/frontend/src/components/manage/MultiPanelCreationForm.svelte
new file mode 100644
index 0000000..2cdfa79
--- /dev/null
+++ b/frontend/src/components/manage/MultiPanelCreationForm.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+
diff --git a/frontend/src/components/manage/MultiPanelEditModal.svelte b/frontend/src/components/manage/MultiPanelEditModal.svelte
new file mode 100644
index 0000000..84ca8d8
--- /dev/null
+++ b/frontend/src/components/manage/MultiPanelEditModal.svelte
@@ -0,0 +1,82 @@
+
+
+
+ Edit Multi-Panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/manage/PanelCreationForm.svelte b/frontend/src/components/manage/PanelCreationForm.svelte
new file mode 100644
index 0000000..eeb9cf7
--- /dev/null
+++ b/frontend/src/components/manage/PanelCreationForm.svelte
@@ -0,0 +1,284 @@
+
+
+
+
+
+
+
+
+
+
+ Toggle Advanced Settings
+
+
+
+
+
+
+
+
+
+
+
Mention On Open
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/manage/PanelEditModal.svelte b/frontend/src/components/manage/PanelEditModal.svelte
new file mode 100644
index 0000000..a04f0bc
--- /dev/null
+++ b/frontend/src/components/manage/PanelEditModal.svelte
@@ -0,0 +1,83 @@
+
+
+
+ Edit Panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/manage/SettingsCard.svelte b/frontend/src/components/manage/SettingsCard.svelte
new file mode 100644
index 0000000..abc432b
--- /dev/null
+++ b/frontend/src/components/manage/SettingsCard.svelte
@@ -0,0 +1,216 @@
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/includes/Auth.svelte b/frontend/src/includes/Auth.svelte
new file mode 100644
index 0000000..87634b3
--- /dev/null
+++ b/frontend/src/includes/Auth.svelte
@@ -0,0 +1,49 @@
+
\ No newline at end of file
diff --git a/frontend/src/includes/Head.svelte b/frontend/src/includes/Head.svelte
new file mode 100644
index 0000000..2e04703
--- /dev/null
+++ b/frontend/src/includes/Head.svelte
@@ -0,0 +1,27 @@
+
+ Tickets | A Discord Support Manager Bot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/includes/LoadingScreen.svelte b/frontend/src/includes/LoadingScreen.svelte
new file mode 100644
index 0000000..18c640f
--- /dev/null
+++ b/frontend/src/includes/LoadingScreen.svelte
@@ -0,0 +1,85 @@
+{#if $loadingScreen}
+
+{/if}
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/includes/Navbar.svelte b/frontend/src/includes/Navbar.svelte
new file mode 100644
index 0000000..4ad7231
--- /dev/null
+++ b/frontend/src/includes/Navbar.svelte
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+ Settings
+ Transcripts
+ Reaction Panels
+ Teams
+ Tickets
+ Blacklist
+ Tags
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/includes/NotifyModal.svelte b/frontend/src/includes/NotifyModal.svelte
new file mode 100644
index 0000000..d07a780
--- /dev/null
+++ b/frontend/src/includes/NotifyModal.svelte
@@ -0,0 +1,96 @@
+{#if $notifyModal}
+
+
+
+ {$notifyTitle}
+
+ {$notifyMessage}
+
+
+
+ Close
+
+
+
+
+
+
+
+
+{/if}
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/includes/Sidebar.svelte b/frontend/src/includes/Sidebar.svelte
new file mode 100644
index 0000000..4702eb6
--- /dev/null
+++ b/frontend/src/includes/Sidebar.svelte
@@ -0,0 +1,168 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/js/constants.js b/frontend/src/js/constants.js
new file mode 100644
index 0000000..32cfbe0
--- /dev/null
+++ b/frontend/src/js/constants.js
@@ -0,0 +1,7 @@
+export const API_URL = env.API_URL || "http://172.26.50.75:3000"
+export const PLACEHOLDER_DOCS_URL = "https://docs.ticketsbot.net/setup/placeholders.html"
+
+export const OAUTH = {
+ clientId: env.CLIENT_ID || "700742994386747404",
+ redirectUri: env.REDIRECT_URI || "http://localhost:5000/callback"
+}
diff --git a/frontend/src/js/stores.js b/frontend/src/js/stores.js
new file mode 100644
index 0000000..3c58d70
--- /dev/null
+++ b/frontend/src/js/stores.js
@@ -0,0 +1,24 @@
+import {get, writable} from "svelte/store";
+
+const loadingCount = writable(0);
+export const loadingScreen = writable(true);
+export const dropdown = writable(false);
+
+export function addLoadingScreenTicket() {
+ loadingCount.update(n => n + 1);
+ loadingScreen.set(true);
+}
+
+export function removeLoadingScreenTicket() {
+ loadingCount.update(n => n - 1);
+ if (get(loadingCount) === 0) {
+ loadingScreen.set(false);
+ }
+}
+
+export const notifyModal = writable(false);
+export const notifyTitle = writable("");
+export const notifyMessage = writable("");
+
+export const isErrorPage = writable(false);
+export const errorMessage = writable("");
\ No newline at end of file
diff --git a/frontend/src/js/util.js b/frontend/src/js/util.js
new file mode 100644
index 0000000..e12cb78
--- /dev/null
+++ b/frontend/src/js/util.js
@@ -0,0 +1,42 @@
+import * as Stores from './stores'
+import {navigateTo} from "svelte-router-spa";
+
+export async function withLoadingScreen(func) {
+ Stores.addLoadingScreenTicket();
+ await func();
+ Stores.removeLoadingScreenTicket();
+}
+
+export function errorPage(message) {
+ navigateTo(`/error?message=${encodeURIComponent(message)}`)
+}
+
+export function notify(title, message) {
+ Stores.notifyTitle.set(title);
+ Stores.notifyMessage.set(message);
+ Stores.notifyModal.set(true);
+}
+
+export function notifyError(message) {
+ notify('Error', message);
+}
+
+export function notifySuccess(message) {
+ notify('Success', message);
+}
+
+export function notifyRatelimit() {
+ notifyError("You're doing that too fast: please wait a few seconds and try again");
+}
+
+export function closeNotificationModal() {
+ Stores.notifyModal.set(false);
+}
+
+export function colourToInt(colour) {
+ return parseInt(`0x${colour.slice(1)}`);
+}
+
+export function intToColour(i) {
+ return `#${i.toString(16)}`
+}
diff --git a/frontend/src/layouts/ErrorPage.svelte b/frontend/src/layouts/ErrorPage.svelte
new file mode 100644
index 0000000..80fbd98
--- /dev/null
+++ b/frontend/src/layouts/ErrorPage.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+ Error
+
+
+
+
+
+
+
+
+
+ Back
+
+ Home
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/IndexLayout.svelte b/frontend/src/layouts/IndexLayout.svelte
new file mode 100644
index 0000000..72921a1
--- /dev/null
+++ b/frontend/src/layouts/IndexLayout.svelte
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/ManageLayout.svelte b/frontend/src/layouts/ManageLayout.svelte
new file mode 100644
index 0000000..0fefb25
--- /dev/null
+++ b/frontend/src/layouts/ManageLayout.svelte
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/TranscriptViewLayout.svelte b/frontend/src/layouts/TranscriptViewLayout.svelte
new file mode 100644
index 0000000..76f5bc1
--- /dev/null
+++ b/frontend/src/layouts/TranscriptViewLayout.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/main.js b/frontend/src/main.js
new file mode 100644
index 0000000..4c473fa
--- /dev/null
+++ b/frontend/src/main.js
@@ -0,0 +1,7 @@
+import App from './App.svelte';
+
+const app = new App({
+ target: document.body
+});
+
+export default app;
\ No newline at end of file
diff --git a/frontend/src/routes.js b/frontend/src/routes.js
new file mode 100644
index 0000000..a7cb4b7
--- /dev/null
+++ b/frontend/src/routes.js
@@ -0,0 +1,83 @@
+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 Index from './views/Index.svelte'
+import LoginCallback from './views/LoginCallback.svelte'
+import Login from './views/Login.svelte'
+import Logout from './views/Logout.svelte'
+import Whitelabel from './views/Whitelabel.svelte'
+import Settings from './views/Settings.svelte'
+import Error from './views/Error.svelte'
+import Error404 from './views/Error404.svelte'
+import Transcripts from './views/Transcripts.svelte'
+import TranscriptView from './views/TranscriptView.svelte'
+import Blacklist from './views/Blacklist.svelte'
+import Panels from './views/Panels.svelte'
+import Tags from './views/Tags.svelte'
+import Teams from './views/Teams.svelte'
+import Tickets from './views/Tickets.svelte'
+import TicketView from './views/TicketView.svelte'
+
+export const routes = [
+ {name: '/', component: Index, layout: IndexLayout},
+ {name: '404', path: '404', component: Error404, layout: ErrorLayout},
+ {name: '/callback', component: LoginCallback},
+ {name: '/login', component: Login},
+ {name: '/logout', component: Logout},
+ {name: '/error', component: Error, layout: ErrorLayout},
+ {name: '/whitelabel', component: Whitelabel, layout: IndexLayout},
+ {
+ name: 'manage/:id',
+ nestedRoutes: [
+ {name: 'index', component: Error404, layout: ErrorLayout},
+ {name: 'settings', component: Settings, layout: ManageLayout},
+ {
+ name: 'transcripts',
+ nestedRoutes: [
+ {
+ name: 'index',
+ component: Transcripts,
+ layout: ManageLayout,
+ },
+ {
+ name: 'view/:ticketid',
+ component: TranscriptView, // just to test
+ layout: TranscriptViewLayout,
+ }
+ ]
+ },
+ // Backwards compatibility
+ {
+ name: 'logs',
+ nestedRoutes: [
+ {
+ name: 'view/:ticketid',
+ component: TranscriptView,
+ layout: TranscriptViewLayout,
+ }
+ ]
+ },
+ {name: 'panels', component: Panels, layout: ManageLayout},
+ {name: 'blacklist', component: Blacklist, layout: ManageLayout},
+ {name: 'tags', component: Tags, layout: ManageLayout},
+ {name: 'teams', component: Teams, layout: ManageLayout},
+ {
+ name: 'tickets',
+ nestedRoutes: [
+ {
+ name: 'index',
+ component: Tickets,
+ layout: ManageLayout,
+ },
+ {
+ name: 'view/:ticketid',
+ component: TicketView,
+ layout: ManageLayout,
+ }
+ ]
+ },
+ ],
+ }
+]
\ No newline at end of file
diff --git a/frontend/src/views/Blacklist.svelte b/frontend/src/views/Blacklist.svelte
new file mode 100644
index 0000000..78497b5
--- /dev/null
+++ b/frontend/src/views/Blacklist.svelte
@@ -0,0 +1,172 @@
+
+
+
+
+ Blacklisted Users
+
+
+
+
+ Username
+ User ID
+ Remove
+
+
+
+ {#each blacklistedUsers as user}
+
+ {#if user.username !== '' && user.discriminator !== ''}
+ {user.username}#{user.discriminator}
+ {:else}
+ Unknown
+ {/if}
+
+ {user.id}
+
+ removeBlacklist(user)}>Remove
+
+
+ {/each}
+
+
+
+
+
+
+
+ Blacklist A User
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Error.svelte b/frontend/src/views/Error.svelte
new file mode 100644
index 0000000..430c120
--- /dev/null
+++ b/frontend/src/views/Error.svelte
@@ -0,0 +1,6 @@
+{message}
+
+
diff --git a/frontend/src/views/Error404.svelte b/frontend/src/views/Error404.svelte
new file mode 100644
index 0000000..98b8502
--- /dev/null
+++ b/frontend/src/views/Error404.svelte
@@ -0,0 +1 @@
+Page not found
\ No newline at end of file
diff --git a/frontend/src/views/Index.svelte b/frontend/src/views/Index.svelte
new file mode 100644
index 0000000..0715c8f
--- /dev/null
+++ b/frontend/src/views/Index.svelte
@@ -0,0 +1,103 @@
+
+
+
+
+ Servers
+
+
+
+
+
+
+ {#each guilds as guild}
+
+ {/each}
+
+
+
+
+ Refresh list
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Login.svelte b/frontend/src/views/Login.svelte
new file mode 100644
index 0000000..e51f6cb
--- /dev/null
+++ b/frontend/src/views/Login.svelte
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/frontend/src/views/LoginCallback.svelte b/frontend/src/views/LoginCallback.svelte
new file mode 100644
index 0000000..3f6b59b
--- /dev/null
+++ b/frontend/src/views/LoginCallback.svelte
@@ -0,0 +1,32 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/Logout.svelte b/frontend/src/views/Logout.svelte
new file mode 100644
index 0000000..0306208
--- /dev/null
+++ b/frontend/src/views/Logout.svelte
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/frontend/src/views/Panels.svelte b/frontend/src/views/Panels.svelte
new file mode 100644
index 0000000..38d8fce
--- /dev/null
+++ b/frontend/src/views/Panels.svelte
@@ -0,0 +1,390 @@
+{#if editModal}
+ editModal = false} on:confirm={submitEdit}/>
+{/if}
+
+{#if multiEditModal}
+ multiEditModal = false} on:confirm={submitMultiPanelEdit}/>
+{/if}
+
+
+
+
+
+ Reaction Panels
+
+
Your panel quota: {panels.length} / {isPremium ? '∞' : '3'}
+
+
+
+
+ Channel
+ Panel Title
+ Ticket Channel Category
+ Edit
+ Delete
+
+
+
+ {#each panels as panel}
+
+ #{channels.find((c) => c.id === panel.channel_id)?.name ?? 'Unknown Channel'}
+ {panel.title}
+ {channels.find((c) => c.id === panel.category_id)?.name ?? 'Unknown Category'}
+
+ openEditModal(panel.panel_id)}>Edit
+
+
+ deletePanel(panel.panel_id)}>Delete
+
+
+ {/each}
+
+
+
+
+
+
+
+ Create Panel
+
+
+ {#if !$loadingScreen}
+
+
+ {/if}
+
+
+
+
+
+
+
+ Multi-Panels
+
+
+
+
+ Embed Title
+ Edit
+ Delete
+
+
+
+ {#each multiPanels as panel}
+
+ {panel.title}
+
+ openMultiEditModal(panel.id)}>Edit
+
+
+ deleteMultiPanel(panel.id)}>Delete
+
+
+ {/each}
+
+
+
+
+
+
+
+ Create A Multi-Panel
+
+
Note: The panels which you wish to combine into a multi-panel must already exist
+
+ {#if !$loadingScreen}
+
+ {/if}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/Settings.svelte b/frontend/src/views/Settings.svelte
new file mode 100644
index 0000000..3167112
--- /dev/null
+++ b/frontend/src/views/Settings.svelte
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/frontend/src/views/Tags.svelte b/frontend/src/views/Tags.svelte
new file mode 100644
index 0000000..75fb74f
--- /dev/null
+++ b/frontend/src/views/Tags.svelte
@@ -0,0 +1,185 @@
+
+
+
+
+ Tags
+
+
+
+
+ Tag
+ Edit
+ Delete
+
+
+
+ {#each Object.entries(tags) as [id, content]}
+
+ {id}
+
+ editTag(id)}>Edit
+
+
+ deleteTag(id)}>Delete
+
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Teams.svelte b/frontend/src/views/Teams.svelte
new file mode 100644
index 0000000..22b5844
--- /dev/null
+++ b/frontend/src/views/Teams.svelte
@@ -0,0 +1,329 @@
+
+
+
+ Support Teams
+
+
+
+
Manage Teams
+
+
+
+
+ {#if activeTeam.id !== 'default'}
+
+ deleteTeam(activeTeam.id)}>Delete {activeTeam.name}
+
+ {/if}
+
+
+
+
+
Manage Members
+
+
+
+ {#each members as member}
+
+ {member.name}
+
+ removeMember(activeTeam.id, member)}>Delete
+
+
+
+ {/each}
+
+
+
+
+
+
Add Member
+
+
+
+
+
+
+ Add To Team
+
+
+
+
+
Add Role
+
+
+
+
+
+
+ Add To Team
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/TicketView.svelte b/frontend/src/views/TicketView.svelte
new file mode 100644
index 0000000..4da7661
--- /dev/null
+++ b/frontend/src/views/TicketView.svelte
@@ -0,0 +1,197 @@
+
+
+
+
+
diff --git a/frontend/src/views/Tickets.svelte b/frontend/src/views/Tickets.svelte
new file mode 100644
index 0000000..0be9faa
--- /dev/null
+++ b/frontend/src/views/Tickets.svelte
@@ -0,0 +1,120 @@
+
+
+
+ Open Tickets
+
+
+
+
+ ID
+ User
+ View
+
+
+
+ {#each tickets as ticket}
+
+ {ticket.id}
+ {#if ticket.user !== undefined}
+ {ticket.user.username}#{ticket.user.discriminator}
+ {:else}
+ Unknown
+ {/if}
+
+
+ View
+
+
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/TranscriptView.svelte b/frontend/src/views/TranscriptView.svelte
new file mode 100644
index 0000000..c6f0a2e
--- /dev/null
+++ b/frontend/src/views/TranscriptView.svelte
@@ -0,0 +1,139 @@
+
+
+
+
+
+ {#each messages as message}
+
+ {#if message.timestamp > epoch}
+
+ [{message.timestamp.toLocaleTimeString([], dateFormatSettings)} {message.timestamp.toLocaleDateString()}]
+
+ {/if}
+
+
+
{message.author.username}
+
{message.content}
+
+ {#if message.attachments !== undefined && message.attachments.length > 0}
+ {#each message.attachments as attachment}
+
+ {/each}
+ {/if}
+
+ {/each}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/Transcripts.svelte b/frontend/src/views/Transcripts.svelte
new file mode 100644
index 0000000..1841cb8
--- /dev/null
+++ b/frontend/src/views/Transcripts.svelte
@@ -0,0 +1,305 @@
+
+
+
+
+
+ Filter Logs
+
+
+
+
+
+
+
+
+ Transcripts
+
+
+
+
+
+
+ Ticket ID
+ Username
+ Close Reason
+ Transcript
+
+
+
+ {#each transcripts as transcript}
+
+ {transcript.ticket_id}
+ {transcript.username}
+ {transcript.close_reason || 'No reason specified'}
+
+
+ View
+
+
+
+ {/each}
+
+
+
+
+
+ Page {page}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Whitelabel.svelte b/frontend/src/views/Whitelabel.svelte
new file mode 100644
index 0000000..7d9936a
--- /dev/null
+++ b/frontend/src/views/Whitelabel.svelte
@@ -0,0 +1,356 @@
+
+
+
+
+ Bot Token
+
+
+ Bot Token
+
+ Note: You will not be able to view the token after submitting it
+
+
+
+
+
+
+
+
+
+
+
+ Custom Status
+
+
+ Status
+
+
+
+ Submit
+
+
+
+
+
+
+
+
+ Error Log
+
+
+
+
+ Error
+ Time
+
+
+
+ {#each errors as error}
+
+ {error.message}
+ {error.time.toLocaleString()}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/go.mod b/go.mod
index 7b0b45d..098faad 100644
--- a/go.mod
+++ b/go.mod
@@ -6,12 +6,11 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/TicketsBot/archiverclient v0.0.0-20210220155137-a562b2f1bbbb
github.com/TicketsBot/common v0.0.0-20210604175952-03cfa14c16e1
- github.com/TicketsBot/database v0.0.0-20210604172320-343ef21cf7e9
+ github.com/TicketsBot/database v0.0.0-20210625163257-35eea2daa97c
github.com/TicketsBot/worker v0.0.0-20210528135955-34744f610804
github.com/apex/log v1.1.2
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/gin-contrib/multitemplate v0.0.0-20200226145339-3e397ee01bc6
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607
github.com/gin-gonic/gin v1.7.1
@@ -21,7 +20,7 @@ require (
github.com/jackc/pgx/v4 v4.7.1
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
github.com/pkg/errors v0.9.1
- github.com/rxdn/gdl v0.0.0-20210602135239-d993749b319d
+ github.com/rxdn/gdl v0.0.0-20210629143956-f248d37cb604
github.com/sirupsen/logrus v1.5.0
github.com/ulule/limiter/v3 v3.5.0
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
diff --git a/messagequeue/webchat.go b/messagequeue/webchat.go
deleted file mode 100644
index db89c84..0000000
--- a/messagequeue/webchat.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package messagequeue
-
-import (
- "encoding/json"
- "fmt"
-)
-
-type TicketMessage struct {
- GuildId string `json:"guild"`
- TicketId int `json:"ticket"`
- Username string `json:"username"`
- Content string `json:"content"`
-}
-
-func (c *RedisClient) ListenForMessages(message chan TicketMessage) {
- pubsub := c.Subscribe("tickets:webchat:inboundmessage")
-
- for {
- msg, err := pubsub.ReceiveMessage(); if err != nil {
- fmt.Println(err.Error())
- continue
- }
-
- var decoded TicketMessage
- if err := json.Unmarshal([]byte(msg.Payload), &decoded); err != nil {
- fmt.Println(err.Error())
- continue
- }
-
- message<-decoded
- }
-}
diff --git a/public/static/css/discordmock.css b/public/static/css/discordmock.css
deleted file mode 100644
index b737a37..0000000
--- a/public/static/css/discordmock.css
+++ /dev/null
@@ -1,63 +0,0 @@
-@font-face{font-family:Whitney;font-style:light;font-weight:300;src:url('https://discordapp.com/assets/6c6374bad0b0b6d204d8d6dc4a18d820.woff') format('woff')}
-@font-face{font-family:Whitney;font-style:normal;font-weight:500;src:url('https://discordapp.com/assets/e8acd7d9bf6207f99350ca9f9e23b168.woff') format('woff')}
-@font-face{font-family:Whitney;font-style:medium;font-weight:600;src:url('https://discordapp.com/assets/3bdef1251a424500c1b3a78dea9b7e57.woff') format('woff')}
-@font-face{font-family:WhitneyMedium;font-style:medium;font-weight:600;src:url('https://discordapp.com/assets/be0060dafb7a0e31d2a1ca17c0708636.woff') format('woff')}
-@font-face{font-family:Whitney;font-style:bold;font-weight:700;src:url('https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff') format('woff')}
-
-.discord-container {
- background-color: #2e3136;
- border-radius: 4px;
- height: 80vh;
- max-height: 100vh;
- margin: 0;
- padding: 0;
- font-family: 'Whitney', sans-serif !important;
-}
-
-.channel-header {
- background-color: #1e2124;
- height: 5vh;
- width: 100%;
- border-radius: 4px 4px 0 0;
- position: relative;
-
- text-align: center;
- display: flex;
- align-items: center;
-}
-
-#channel-name {
- color: white;
- padding-left: 20px;
-}
-
-#message-container {
- height: 70vh;
- max-height: 70vh;
- position: relative;
- overflow: scroll;
- overflow-x: hidden;
-}
-
-.message {
- color: white !important;
- padding-left: 20px;
-}
-
-.message-input {
- padding-top: 20px !important;
- border-color: #2e3136 !important;
- padding-left: 5px !important;
- padding-right: 5px !important;
- background-color: #2e3136 !important;
- color: white !important;
- height: 100%;
- max-height: 100%;
- min-height: 100%;
-}
-
-.message-input:focus {
- border-color: #2e3136 !important;
- background-color: #2e3136 !important;
- color: white !important;
-}
diff --git a/public/static/css/light-bootstrap-dashboard.css b/public/static/css/light-bootstrap-dashboard.css
deleted file mode 100644
index 8ea4f4c..0000000
--- a/public/static/css/light-bootstrap-dashboard.css
+++ /dev/null
@@ -1,3863 +0,0 @@
-/*!
-
- =========================================================
- * Light Bootstrap Dashboard - v1.4.0
- =========================================================
-
- * Product Page: http://www.creative-tim.com/product/light-bootstrap-dashboard
- * Copyright 2017 Creative Tim (http://www.creative-tim.com)
- * Licensed under MIT (https://github.com/creativetimofficial/light-bootstrap-dashboard/blob/master/LICENSE.md)
-
- =========================================================
-
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
- */
-/* light colors */
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@-webkit-keyframes spin {
- from {
- -webkit-transform: rotate(0deg);
- }
- to {
- -webkit-transform: rotate(360deg);
- }
-}
-
-@-moz-keyframes spin {
- from {
- -moz-transform: rotate(0deg);
- }
- to {
- -moz-transform: rotate(360deg);
- }
-}
-
-@-ms-keyframes spin {
- from {
- -ms-transform: rotate(0deg);
- }
- to {
- -ms-transform: rotate(360deg);
- }
-}
-
-/* Font Smoothing */
-body,
-h1, .h1,
-h2, .h2,
-h3, .h3,
-h4, .h4,
-h5, .h5,
-h6, .h6,
-p,
-.navbar,
-.brand,
-.btn-simple,
-.alert,
-a,
-.td-name,
-td,
-button.close {
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: antialiased;
- font-family: "Roboto","Helvetica Neue",Arial,sans-serif;
- font-weight: 400;
-}
-
-h1, .h1, h2, .h2, h3, .h3, h4, .h4 {
- font-weight: 300;
-}
-
-h1, .h1 {
- font-size: 52px;
-}
-
-h2, .h2 {
- font-size: 36px;
-}
-
-h3, .h3 {
- font-size: 28px;
- margin: 20px 0 10px;
-}
-
-h4, .h4 {
- font-size: 22px;
- line-height: 30px;
-}
-
-h5, .h5 {
- font-size: 16px;
- margin-bottom: 15px;
-}
-
-h6, .h6 {
- font-size: 14px;
- font-weight: 600;
- text-transform: uppercase;
-}
-
-p {
- font-size: 16px;
- line-height: 1.5;
-}
-
-h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small, h1 .small, h2 .small, h3 .small, h4 .small, h5 .small, h6 .small, .h1 .small, .h2 .small, .h3 .small, .h4 .small, .h5 .small, .h6 .small {
- color: #9A9A9A;
- font-weight: 300;
- line-height: 1.5;
-}
-
-h1 small, h2 small, h3 small, h1 .small, h2 .small, h3 .small {
- font-size: 60%;
-}
-
-h1 .subtitle {
- display: block;
- margin: 0 0 30px;
-}
-
-.text-muted {
- color: #9A9A9A;
-}
-
-.text-primary, .text-primary:hover {
- color: #1D62F0 !important;
-}
-
-.text-info, .text-info:hover {
- color: #1DC7EA !important;
-}
-
-.text-success, .text-success:hover {
- color: #87CB16 !important;
-}
-
-.text-warning, .text-warning:hover {
- color: #FF9500 !important;
-}
-
-.text-danger, .text-danger:hover {
- color: #FF4A55 !important;
-}
-
-/* General overwrite */
-body,
-.wrapper {
- min-height: 100vh;
- position: relative;
-}
-
-body {
- background: rgba(203, 203, 210, 0.15);
-}
-
-a {
- color: #1DC7EA;
-}
-
-a:hover, a:focus {
- color: #42d0ed;
- text-decoration: none;
-}
-
-a:focus, a:active,
-button::-moz-focus-inner,
-input::-moz-focus-inner,
-input[type="reset"]::-moz-focus-inner,
-input[type="button"]::-moz-focus-inner,
-input[type="submit"]::-moz-focus-inner,
-select::-moz-focus-inner,
-input[type="file"] > input[type="button"]::-moz-focus-inner {
- outline: 0;
-}
-
-.ui-slider-handle:focus,
-.navbar-toggle,
-input:focus {
- outline: 0 !important;
-}
-
-/* Animations */
-.form-control,
-.input-group-addon,
-.tagsinput,
-.navbar,
-.navbar .alert {
- -webkit-transition: all 300ms linear;
- -moz-transition: all 300ms linear;
- -o-transition: all 300ms linear;
- -ms-transition: all 300ms linear;
- transition: all 300ms linear;
-}
-
-.sidebar .nav a,
-.table > tbody > tr .td-actions .btn {
- -webkit-transition: all 150ms ease-in;
- -moz-transition: all 150ms ease-in;
- -o-transition: all 150ms ease-in;
- -ms-transition: all 150ms ease-in;
- transition: all 150ms ease-in;
-}
-
-.btn {
- -webkit-transition: all 100ms ease-in;
- -moz-transition: all 100ms ease-in;
- -o-transition: all 100ms ease-in;
- -ms-transition: all 100ms ease-in;
- transition: all 100ms ease-in;
-}
-
-.fa {
- width: 18px;
- text-align: center;
-}
-
-.margin-top {
- margin-top: 50px;
-}
-
-.wrapper {
- position: relative;
- top: 0;
- height: 100vh;
-}
-
-.sidebar {
- position: fixed;
- top: 0;
- bottom: 0;
- right: auto;
- left: 0;
- width: 260px;
- display: block;
- z-index: 1;
- color: #fff;
- font-weight: 200;
- background-size: cover;
- background-position: center center;
-}
-
-.sidebar .sidebar-wrapper {
- position: relative;
- max-height: calc(100vh - 75px);
- min-height: 100%;
- overflow: auto;
- width: 260px;
- z-index: 4;
- padding-bottom: 100px;
-}
-
-.sidebar .sidebar-background {
- position: absolute;
- z-index: 1;
- height: 100%;
- width: 100%;
- display: block;
- top: 0;
- left: 0;
- background-size: cover;
- background-position: center center;
-}
-
-.sidebar .logo {
- padding: 10px 15px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.sidebar .logo p {
- float: left;
- font-size: 20px;
- margin: 10px 10px;
- color: #FFFFFF;
- line-height: 20px;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-}
-
-.sidebar .logo .simple-text {
- text-transform: uppercase;
- padding: 5px 0px;
- display: block;
- font-size: 18px;
- color: #FFFFFF;
- text-align: center;
- font-weight: 400;
- line-height: 30px;
-}
-
-.sidebar .logo-tim {
- border-radius: 50%;
- border: 1px solid #333;
- display: block;
- height: 61px;
- width: 61px;
- float: left;
- overflow: hidden;
-}
-
-.sidebar .logo-tim img {
- width: 60px;
- height: 60px;
-}
-
-.sidebar .nav {
- margin-top: 20px;
- float: none;
-}
-
-.sidebar .nav .open > a,
-.sidebar .nav li.dropdown .dropdown-menu li:hover > a,
-.sidebar .nav li:hover > a {
- background-color: rgba(255, 255, 255, 0.13);
- opacity: 1;
-}
-
-.sidebar .nav li > a {
- color: #FFFFFF;
- margin: 5px 15px;
- opacity: .86;
- border-radius: 4px;
- display: block;
-}
-
-.sidebar .nav li.active > a {
- color: #FFFFFF;
- opacity: 1;
- background: rgba(255, 255, 255, 0.23);
-}
-
-.sidebar .nav li.separator {
- margin: 15px 0;
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.sidebar .nav li.separator + li {
- margin-top: 31px;
-}
-
-.sidebar .nav p {
- margin: 0;
- line-height: 30px;
- font-size: 12px;
- font-weight: 600;
- text-transform: uppercase;
- margin-left: 45px;
-}
-
-.sidebar .nav i {
- font-size: 28px;
- float: left;
- margin-right: 15px;
- line-height: 30px;
- width: 30px;
- text-align: center;
-}
-
-.sidebar .nav .caret {
- margin-top: 13px;
- position: absolute;
- right: 30px;
-}
-
-.sidebar .logo {
- padding: 10px 15px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.sidebar .logo p {
- float: left;
- font-size: 20px;
- margin: 10px 10px;
- color: #FFFFFF;
- line-height: 20px;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-}
-
-.sidebar .logo .simple-text {
- text-transform: uppercase;
- padding: 5px 0px;
- display: block;
- font-size: 18px;
- color: #FFFFFF;
- text-align: center;
- font-weight: 400;
- line-height: 30px;
-}
-
-.sidebar .logo-tim {
- border-radius: 50%;
- border: 1px solid #333;
- display: block;
- height: 61px;
- width: 61px;
- float: left;
- overflow: hidden;
-}
-
-.sidebar .logo-tim img {
- width: 60px;
- height: 60px;
-}
-
-.sidebar:after, .sidebar:before {
- display: block;
- content: "";
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- z-index: 2;
-}
-
-.sidebar:before {
- opacity: .33;
- background: #000000;
-}
-
-.sidebar:after {
- background: #787878;
- background: -moz-linear-gradient(top, #787878 0%, #343434 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #787878), color-stop(100%, #343434));
- background: -webkit-linear-gradient(top, #787878 0%, #343434 100%);
- background: -o-linear-gradient(top, #787878 0%, #343434 100%);
- background: -ms-linear-gradient(top, #787878 0%, #343434 100%);
- background: linear-gradient(to bottom, #787878 0%, #343434 100%);
- background-size: 150% 150%;
- z-index: 3;
- opacity: 1;
-}
-
-.sidebar[data-image]:after, .sidebar.has-image:after {
- opacity: .77;
-}
-
-.sidebar[data-color="blue"]:after {
- background: #1F77D0;
- background: -moz-linear-gradient(top, #1F77D0 0%, #533ce1 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #1F77D0), color-stop(100%, #533ce1));
- background: -webkit-linear-gradient(top, #1F77D0 0%, #533ce1 100%);
- background: -o-linear-gradient(top, #1F77D0 0%, #533ce1 100%);
- background: -ms-linear-gradient(top, #1F77D0 0%, #533ce1 100%);
- background: linear-gradient(to bottom, #1F77D0 0%, #533ce1 100%);
- background-size: 150% 150%;
-}
-
-.sidebar[data-color="azure"]:after {
- background: #1DC7EA;
- background: -moz-linear-gradient(top, #1DC7EA 0%, #4091ff 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #1DC7EA), color-stop(100%, #4091ff));
- background: -webkit-linear-gradient(top, #1DC7EA 0%, #4091ff 100%);
- background: -o-linear-gradient(top, #1DC7EA 0%, #4091ff 100%);
- background: -ms-linear-gradient(top, #1DC7EA 0%, #4091ff 100%);
- background: linear-gradient(to bottom, #1DC7EA 0%, #4091ff 100%);
- background-size: 150% 150%;
-}
-
-.sidebar[data-color="green"]:after {
- background: #87CB16;
- background: -moz-linear-gradient(top, #87CB16 0%, #6dc030 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #87CB16), color-stop(100%, #6dc030));
- background: -webkit-linear-gradient(top, #87CB16 0%, #6dc030 100%);
- background: -o-linear-gradient(top, #87CB16 0%, #6dc030 100%);
- background: -ms-linear-gradient(top, #87CB16 0%, #6dc030 100%);
- background: linear-gradient(to bottom, #87CB16 0%, #6dc030 100%);
- background-size: 150% 150%;
-}
-
-.sidebar[data-color="orange"]:after {
- background: #FFA534;
- background: -moz-linear-gradient(top, #FFA534 0%, #ff5221 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FFA534), color-stop(100%, #ff5221));
- background: -webkit-linear-gradient(top, #FFA534 0%, #ff5221 100%);
- background: -o-linear-gradient(top, #FFA534 0%, #ff5221 100%);
- background: -ms-linear-gradient(top, #FFA534 0%, #ff5221 100%);
- background: linear-gradient(to bottom, #FFA534 0%, #ff5221 100%);
- background-size: 150% 150%;
-}
-
-.sidebar[data-color="red"]:after {
- background: #FB404B;
- background: -moz-linear-gradient(top, #FB404B 0%, #bb0502 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FB404B), color-stop(100%, #bb0502));
- background: -webkit-linear-gradient(top, #FB404B 0%, #bb0502 100%);
- background: -o-linear-gradient(top, #FB404B 0%, #bb0502 100%);
- background: -ms-linear-gradient(top, #FB404B 0%, #bb0502 100%);
- background: linear-gradient(to bottom, #FB404B 0%, #bb0502 100%);
- background-size: 150% 150%;
-}
-
-.sidebar[data-color="purple"]:after {
- background: #9368E9;
- background: -moz-linear-gradient(top, #9368E9 0%, #943bea 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #9368E9), color-stop(100%, #943bea));
- background: -webkit-linear-gradient(top, #9368E9 0%, #943bea 100%);
- background: -o-linear-gradient(top, #9368E9 0%, #943bea 100%);
- background: -ms-linear-gradient(top, #9368E9 0%, #943bea 100%);
- background: linear-gradient(to bottom, #9368E9 0%, #943bea 100%);
- background-size: 150% 150%;
-}
-
-.main-panel {
- position: relative;
- z-index: 2;
- float: right;
- width: calc(100% - 260px);
-}
-
-.main-panel > .content {
- padding: 30px 15px;
- min-height: calc(100% - 123px);
-}
-
-.main-panel > .footer {
- border-top: 1px solid #e7e7e7;
-}
-
-.main-panel .navbar {
- margin-bottom: 0;
-}
-
-.sidebar,
-.main-panel {
- max-height: 100%;
- height: 100%;
- -webkit-transition-property: top,bottom;
- transition-property: top,bottom;
- -webkit-transition-duration: .2s,.2s;
- transition-duration: .2s,.2s;
- -webkit-transition-timing-function: linear,linear;
- transition-timing-function: linear,linear;
- -webkit-overflow-scrolling: touch;
-}
-
-.btn {
- border-width: 2px;
- background-color: transparent;
- font-weight: 400;
- opacity: 0.8;
- filter: alpha(opacity=80);
- padding: 8px 16px;
- border-color: #888888;
- color: #888888;
-}
-
-.btn:hover, .btn:focus, .btn:active, .btn.active,
-.open > .btn.dropdown-toggle {
- background-color: transparent;
- color: #777777;
- border-color: #777777;
-}
-
-.btn.disabled, .btn.disabled:hover, .btn.disabled:focus, .btn.disabled.focus, .btn.disabled:active, .btn.disabled.active, .btn:disabled, .btn:disabled:hover, .btn:disabled:focus, .btn:disabled.focus, .btn:disabled:active, .btn:disabled.active, .btn[disabled], .btn[disabled]:hover, .btn[disabled]:focus, .btn[disabled].focus, .btn[disabled]:active, .btn[disabled].active,
-fieldset[disabled] .btn,
-fieldset[disabled] .btn:hover,
-fieldset[disabled] .btn:focus,
-fieldset[disabled] .btn.focus,
-fieldset[disabled] .btn:active,
-fieldset[disabled] .btn.active {
- background-color: transparent;
- border-color: #888888;
-}
-
-.btn.btn-fill {
- color: #FFFFFF;
- background-color: #888888;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn.btn-fill:hover, .btn.btn-fill:focus, .btn.btn-fill:active, .btn.btn-fill.active,
-.open > .btn.btn-fill.dropdown-toggle {
- background-color: #777777;
- color: #FFFFFF;
-}
-
-.btn.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn .caret {
- border-top-color: #888888;
-}
-
-.btn:hover, .btn:focus {
- opacity: 1;
- filter: alpha(opacity=100);
- outline: 0 !important;
-}
-
-.btn:active, .btn.active,
-.open > .btn.dropdown-toggle {
- -webkit-box-shadow: none;
- box-shadow: none;
- outline: 0 !important;
-}
-
-.btn.btn-icon {
- padding: 8px;
-}
-
-.btn-primary {
- border-color: #3472F7;
- color: #3472F7;
-}
-
-.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active,
-.open > .btn-primary.dropdown-toggle {
- background-color: transparent;
- color: #1D62F0;
- border-color: #1D62F0;
-}
-
-.btn-primary.disabled, .btn-primary.disabled:hover, .btn-primary.disabled:focus, .btn-primary.disabled.focus, .btn-primary.disabled:active, .btn-primary.disabled.active, .btn-primary:disabled, .btn-primary:disabled:hover, .btn-primary:disabled:focus, .btn-primary:disabled.focus, .btn-primary:disabled:active, .btn-primary:disabled.active, .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:focus, .btn-primary[disabled].focus, .btn-primary[disabled]:active, .btn-primary[disabled].active,
-fieldset[disabled] .btn-primary,
-fieldset[disabled] .btn-primary:hover,
-fieldset[disabled] .btn-primary:focus,
-fieldset[disabled] .btn-primary.focus,
-fieldset[disabled] .btn-primary:active,
-fieldset[disabled] .btn-primary.active {
- background-color: transparent;
- border-color: #3472F7;
-}
-
-.btn-primary.btn-fill {
- color: #FFFFFF;
- background-color: #3472F7;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-primary.btn-fill:hover, .btn-primary.btn-fill:focus, .btn-primary.btn-fill:active, .btn-primary.btn-fill.active,
-.open > .btn-primary.btn-fill.dropdown-toggle {
- background-color: #1D62F0;
- color: #FFFFFF;
-}
-
-.btn-primary.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-primary .caret {
- border-top-color: #3472F7;
-}
-
-.btn-success {
- border-color: #87CB16;
- color: #87CB16;
-}
-
-.btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active,
-.open > .btn-success.dropdown-toggle {
- background-color: transparent;
- color: #049F0C;
- border-color: #049F0C;
-}
-
-.btn-success.disabled, .btn-success.disabled:hover, .btn-success.disabled:focus, .btn-success.disabled.focus, .btn-success.disabled:active, .btn-success.disabled.active, .btn-success:disabled, .btn-success:disabled:hover, .btn-success:disabled:focus, .btn-success:disabled.focus, .btn-success:disabled:active, .btn-success:disabled.active, .btn-success[disabled], .btn-success[disabled]:hover, .btn-success[disabled]:focus, .btn-success[disabled].focus, .btn-success[disabled]:active, .btn-success[disabled].active,
-fieldset[disabled] .btn-success,
-fieldset[disabled] .btn-success:hover,
-fieldset[disabled] .btn-success:focus,
-fieldset[disabled] .btn-success.focus,
-fieldset[disabled] .btn-success:active,
-fieldset[disabled] .btn-success.active {
- background-color: transparent;
- border-color: #87CB16;
-}
-
-.btn-success.btn-fill {
- color: #FFFFFF;
- background-color: #87CB16;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-success.btn-fill:hover, .btn-success.btn-fill:focus, .btn-success.btn-fill:active, .btn-success.btn-fill.active,
-.open > .btn-success.btn-fill.dropdown-toggle {
- background-color: #049F0C;
- color: #FFFFFF;
-}
-
-.btn-success.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-success .caret {
- border-top-color: #87CB16;
-}
-
-.btn-info {
- border-color: #1DC7EA;
- color: #1DC7EA;
-}
-
-.btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active,
-.open > .btn-info.dropdown-toggle {
- background-color: transparent;
- color: #42d0ed;
- border-color: #42d0ed;
-}
-
-.btn-info.disabled, .btn-info.disabled:hover, .btn-info.disabled:focus, .btn-info.disabled.focus, .btn-info.disabled:active, .btn-info.disabled.active, .btn-info:disabled, .btn-info:disabled:hover, .btn-info:disabled:focus, .btn-info:disabled.focus, .btn-info:disabled:active, .btn-info:disabled.active, .btn-info[disabled], .btn-info[disabled]:hover, .btn-info[disabled]:focus, .btn-info[disabled].focus, .btn-info[disabled]:active, .btn-info[disabled].active,
-fieldset[disabled] .btn-info,
-fieldset[disabled] .btn-info:hover,
-fieldset[disabled] .btn-info:focus,
-fieldset[disabled] .btn-info.focus,
-fieldset[disabled] .btn-info:active,
-fieldset[disabled] .btn-info.active {
- background-color: transparent;
- border-color: #1DC7EA;
-}
-
-.btn-info.btn-fill {
- color: #FFFFFF;
- background-color: #1DC7EA;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-info.btn-fill:hover, .btn-info.btn-fill:focus, .btn-info.btn-fill:active, .btn-info.btn-fill.active,
-.open > .btn-info.btn-fill.dropdown-toggle {
- background-color: #42d0ed;
- color: #FFFFFF;
-}
-
-.btn-info.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-info .caret {
- border-top-color: #1DC7EA;
-}
-
-.btn-warning {
- border-color: #FF9500;
- color: #FF9500;
-}
-
-.btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active,
-.open > .btn-warning.dropdown-toggle {
- background-color: transparent;
- color: #ED8D00;
- border-color: #ED8D00;
-}
-
-.btn-warning.disabled, .btn-warning.disabled:hover, .btn-warning.disabled:focus, .btn-warning.disabled.focus, .btn-warning.disabled:active, .btn-warning.disabled.active, .btn-warning:disabled, .btn-warning:disabled:hover, .btn-warning:disabled:focus, .btn-warning:disabled.focus, .btn-warning:disabled:active, .btn-warning:disabled.active, .btn-warning[disabled], .btn-warning[disabled]:hover, .btn-warning[disabled]:focus, .btn-warning[disabled].focus, .btn-warning[disabled]:active, .btn-warning[disabled].active,
-fieldset[disabled] .btn-warning,
-fieldset[disabled] .btn-warning:hover,
-fieldset[disabled] .btn-warning:focus,
-fieldset[disabled] .btn-warning.focus,
-fieldset[disabled] .btn-warning:active,
-fieldset[disabled] .btn-warning.active {
- background-color: transparent;
- border-color: #FF9500;
-}
-
-.btn-warning.btn-fill {
- color: #FFFFFF;
- background-color: #FF9500;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-warning.btn-fill:hover, .btn-warning.btn-fill:focus, .btn-warning.btn-fill:active, .btn-warning.btn-fill.active,
-.open > .btn-warning.btn-fill.dropdown-toggle {
- background-color: #ED8D00;
- color: #FFFFFF;
-}
-
-.btn-warning.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-warning .caret {
- border-top-color: #FF9500;
-}
-
-.btn-danger {
- border-color: #FF4A55;
- color: #FF4A55;
-}
-
-.btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active,
-.open > .btn-danger.dropdown-toggle {
- background-color: transparent;
- color: #EE2D20;
- border-color: #EE2D20;
-}
-
-.btn-danger.disabled, .btn-danger.disabled:hover, .btn-danger.disabled:focus, .btn-danger.disabled.focus, .btn-danger.disabled:active, .btn-danger.disabled.active, .btn-danger:disabled, .btn-danger:disabled:hover, .btn-danger:disabled:focus, .btn-danger:disabled.focus, .btn-danger:disabled:active, .btn-danger:disabled.active, .btn-danger[disabled], .btn-danger[disabled]:hover, .btn-danger[disabled]:focus, .btn-danger[disabled].focus, .btn-danger[disabled]:active, .btn-danger[disabled].active,
-fieldset[disabled] .btn-danger,
-fieldset[disabled] .btn-danger:hover,
-fieldset[disabled] .btn-danger:focus,
-fieldset[disabled] .btn-danger.focus,
-fieldset[disabled] .btn-danger:active,
-fieldset[disabled] .btn-danger.active {
- background-color: transparent;
- border-color: #FF4A55;
-}
-
-.btn-danger.btn-fill {
- color: #FFFFFF;
- background-color: #FF4A55;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-danger.btn-fill:hover, .btn-danger.btn-fill:focus, .btn-danger.btn-fill:active, .btn-danger.btn-fill.active,
-.open > .btn-danger.btn-fill.dropdown-toggle {
- background-color: #EE2D20;
- color: #FFFFFF;
-}
-
-.btn-danger.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-danger .caret {
- border-top-color: #FF4A55;
-}
-
-.btn-neutral {
- border-color: #FFFFFF;
- color: #FFFFFF;
-}
-
-.btn-neutral:hover, .btn-neutral:focus, .btn-neutral:active, .btn-neutral.active,
-.open > .btn-neutral.dropdown-toggle {
- background-color: transparent;
- color: #FFFFFF;
- border-color: #FFFFFF;
-}
-
-.btn-neutral.disabled, .btn-neutral.disabled:hover, .btn-neutral.disabled:focus, .btn-neutral.disabled.focus, .btn-neutral.disabled:active, .btn-neutral.disabled.active, .btn-neutral:disabled, .btn-neutral:disabled:hover, .btn-neutral:disabled:focus, .btn-neutral:disabled.focus, .btn-neutral:disabled:active, .btn-neutral:disabled.active, .btn-neutral[disabled], .btn-neutral[disabled]:hover, .btn-neutral[disabled]:focus, .btn-neutral[disabled].focus, .btn-neutral[disabled]:active, .btn-neutral[disabled].active,
-fieldset[disabled] .btn-neutral,
-fieldset[disabled] .btn-neutral:hover,
-fieldset[disabled] .btn-neutral:focus,
-fieldset[disabled] .btn-neutral.focus,
-fieldset[disabled] .btn-neutral:active,
-fieldset[disabled] .btn-neutral.active {
- background-color: transparent;
- border-color: #FFFFFF;
-}
-
-.btn-neutral.btn-fill {
- color: #FFFFFF;
- background-color: #FFFFFF;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.btn-neutral.btn-fill:hover, .btn-neutral.btn-fill:focus, .btn-neutral.btn-fill:active, .btn-neutral.btn-fill.active,
-.open > .btn-neutral.btn-fill.dropdown-toggle {
- background-color: #FFFFFF;
- color: #FFFFFF;
-}
-
-.btn-neutral.btn-fill .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-neutral .caret {
- border-top-color: #FFFFFF;
-}
-
-.btn-neutral:active, .btn-neutral.active,
-.open > .btn-neutral.dropdown-toggle {
- background-color: #FFFFFF;
- color: #888888;
-}
-
-.btn-neutral.btn-fill, .btn-neutral.btn-fill:hover, .btn-neutral.btn-fill:focus {
- color: #888888;
-}
-
-.btn-neutral.btn-simple:active, .btn-neutral.btn-simple.active {
- background-color: transparent;
-}
-
-.btn:disabled, .btn[disabled], .btn.disabled {
- opacity: 0.5;
- filter: alpha(opacity=50);
-}
-
-.btn-round {
- border-width: 1px;
- border-radius: 30px !important;
- padding: 9px 18px;
-}
-
-.btn-round.btn-icon {
- padding: 9px;
-}
-
-.btn-simple {
- border: 0;
- font-size: 16px;
- padding: 8px 16px;
-}
-
-.btn-simple.btn-icon {
- padding: 8px;
-}
-
-.btn-lg {
- font-size: 18px;
- border-radius: 6px;
- padding: 14px 30px;
- font-weight: 400;
-}
-
-.btn-lg.btn-round {
- padding: 15px 30px;
-}
-
-.btn-lg.btn-simple {
- padding: 16px 30px;
-}
-
-.btn-sm {
- font-size: 12px;
- border-radius: 3px;
- padding: 5px 10px;
-}
-
-.btn-sm.btn-round {
- padding: 6px 10px;
-}
-
-.btn-sm.btn-simple {
- padding: 7px 10px;
-}
-
-.btn-xs {
- font-size: 12px;
- border-radius: 3px;
- padding: 1px 5px;
-}
-
-.btn-xs.btn-round {
- padding: 2px 5px;
-}
-
-.btn-xs.btn-simple {
- padding: 3px 5px;
-}
-
-.btn-wd {
- min-width: 140px;
-}
-
-.btn-group.select {
- width: 100%;
-}
-
-.btn-group.select .btn {
- text-align: left;
-}
-
-.btn-group.select .caret {
- position: absolute;
- top: 50%;
- margin-top: -1px;
- right: 8px;
-}
-
-.form-control::-moz-placeholder {
- color: #DDDDDD;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.form-control:-moz-placeholder {
- color: #DDDDDD;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.form-control::-webkit-input-placeholder {
- color: #DDDDDD;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.form-control:-ms-input-placeholder {
- color: #DDDDDD;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.form-control {
- background-color: #FFFFFF;
- border: 1px solid #E3E3E3;
- border-radius: 4px;
- color: #565656;
- padding: 8px 12px;
- height: 40px;
- -webkit-box-shadow: none;
- box-shadow: none;
-}
-
-.form-control:focus {
- background-color: #FFFFFF;
- border: 1px solid #AAAAAA;
- -webkit-box-shadow: none;
- box-shadow: none;
- outline: 0 !important;
- color: #333333;
-}
-
-.has-success .form-control,
-.has-error .form-control,
-.has-success .form-control:focus,
-.has-error .form-control:focus {
- border-color: #E3E3E3;
- -webkit-box-shadow: none;
- box-shadow: none;
-}
-
-.has-success .form-control {
- color: #87CB16;
-}
-
-.has-success .form-control:focus {
- border-color: #87CB16;
-}
-
-.has-error .form-control {
- color: #FF4A55;
-}
-
-.has-error .form-control:focus {
- border-color: #FF4A55;
-}
-
-.form-control + .form-control-feedback {
- border-radius: 6px;
- font-size: 14px;
- margin-top: -7px;
- position: absolute;
- right: 10px;
- top: 50%;
- vertical-align: middle;
-}
-
-.open .form-control {
- border-radius: 4px 4px 0 0;
- border-bottom-color: transparent;
-}
-
-.input-lg {
- height: 55px;
- padding: 14px 30px;
-}
-
-.has-error .form-control-feedback {
- color: #FF4A55;
-}
-
-.has-success .form-control-feedback {
- color: #87CB16;
-}
-
-.input-group-addon {
- background-color: #FFFFFF;
- border: 1px solid #E3E3E3;
- border-radius: 4px;
-}
-
-.has-success .input-group-addon,
-.has-error .input-group-addon {
- background-color: #FFFFFF;
- border: 1px solid #E3E3E3;
-}
-
-.has-error .form-control:focus + .input-group-addon {
- border-color: #FF4A55;
- color: #FF4A55;
-}
-
-.has-success .form-control:focus + .input-group-addon {
- border-color: #87CB16;
- color: #87CB16;
-}
-
-.form-control:focus + .input-group-addon,
-.form-control:focus ~ .input-group-addon {
- background-color: #FFFFFF;
- border-color: #9A9A9A;
-}
-
-.input-group .form-control:first-child,
-.input-group-addon:first-child,
-.input-group-btn:first-child > .dropdown-toggle,
-.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
- border-right: 0 none;
-}
-
-.input-group .form-control:last-child,
-.input-group-addon:last-child,
-.input-group-btn:last-child > .dropdown-toggle,
-.input-group-btn:first-child > .btn:not(:first-child) {
- border-left: 0 none;
-}
-
-.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
- background-color: #F5F5F5;
- color: #888888;
- cursor: not-allowed;
-}
-
-.input-group-btn .btn {
- border-width: 1px;
- padding: 9px 16px;
-}
-
-.input-group-btn .btn-default:not(.btn-fill) {
- border-color: #DDDDDD;
-}
-
-.input-group-btn:last-child > .btn {
- margin-left: 0;
-}
-
-.input-group-focus .input-group-addon {
- border-color: #9A9A9A;
-}
-
-.alert {
- border: 0;
- border-radius: 0;
- color: #FFFFFF;
- padding: 10px 15px;
- font-size: 14px;
-}
-
-.container .alert {
- border-radius: 4px;
-}
-
-.navbar .alert {
- border-radius: 0;
- left: 0;
- position: absolute;
- right: 0;
- top: 85px;
- width: 100%;
- z-index: 3;
-}
-
-.navbar:not(.navbar-transparent) .alert {
- top: 70px;
-}
-
-.alert span[data-notify="icon"] {
- font-size: 30px;
- display: block;
- left: 15px;
- position: absolute;
- top: 50%;
- margin-top: -15px;
-}
-
-.alert button.close {
- position: absolute;
- right: 10px;
- top: 50%;
- margin-top: -13px;
- z-index: 1033;
- background-color: #FFFFFF;
- display: block;
- border-radius: 50%;
- opacity: .4;
- line-height: 11px;
- width: 25px;
- height: 25px;
- outline: 0 !important;
- text-align: center;
- padding: 3px;
- padding-top: 1px;
- font-weight: 300;
-}
-
-.alert button.close:hover {
- opacity: .55;
-}
-
-.alert .close ~ span {
- display: block;
- max-width: 89%;
-}
-
-.alert[data-notify="container"] {
- padding: 10px 10px 10px 20px;
- border-radius: 4px;
-}
-
-.alert.alert-with-icon {
- padding-left: 65px;
-}
-
-.alert-info {
- background-color: #63d8f1;
-}
-
-.alert-success {
- background-color: #a1e82c;
-}
-
-.alert-warning {
- background-color: #ffbc67;
-}
-
-.alert-danger {
- background-color: #fc727a;
-}
-
-.table .radio,
-.table .checkbox {
- position: relative;
- height: 20px;
- display: block;
- width: 20px;
- padding: 0px 0px;
- margin: 0px 5px;
- text-align: center;
-}
-
-.table .radio .icons,
-.table .checkbox .icons {
- left: 5px;
-}
-
-.table > thead > tr > th,
-.table > tbody > tr > th,
-.table > tfoot > tr > th,
-.table > thead > tr > td,
-.table > tbody > tr > td,
-.table > tfoot > tr > td {
- padding: 12px 8px;
- vertical-align: middle;
-}
-
-.table > thead > tr > th {
- border-bottom-width: 1px;
- font-size: 12px;
- text-transform: uppercase;
- color: #9A9A9A;
- font-weight: 400;
- padding-bottom: 5px;
-}
-
-.table .td-actions .btn {
- opacity: 0.36;
- filter: alpha(opacity=36);
-}
-
-.table .td-actions .btn.btn-xs {
- padding-left: 3px;
- padding-right: 3px;
-}
-
-.table .td-actions {
- min-width: 90px;
-}
-
-.table > tbody > tr {
- position: relative;
-}
-
-.table > tbody > tr:hover .td-actions .btn {
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-/* Checkbox and radio */
-.checkbox,
-.radio {
- margin-bottom: 12px;
-}
-
-.checkbox label,
-.radio label {
- display: inline-block;
- position: relative;
- cursor: pointer;
- padding-left: 24px;
- margin-bottom: 0;
-}
-
-.checkbox label::before,
-.checkbox label::after {
- font-family: 'FontAwesome';
- content: "\f096";
- display: inline-block;
- position: absolute;
- width: 20px;
- height: 20px;
- left: -1px;
- cursor: pointer;
- line-height: 19px;
- font-size: 20px;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- top: 1px;
- color: #DDDDDD;
- transition: color 0.2s linear;
- padding: 1px;
-}
-
-.checkbox label::after {
- content: "";
- text-align: center;
- opacity: 1;
- left: 0;
- color: #DDDDDD;
-}
-
-.checkbox input[type="checkbox"],
-.radio input[type="radio"] {
- opacity: 0;
- margin-left: 0;
-}
-
-.checkbox input[type="checkbox"]:checked + label::after {
- font-family: 'FontAwesome';
- content: "\f046";
-}
-
-.checkbox input[type="checkbox"]:checked + label::after {
- color: #1DC7EA;
-}
-
-.checkbox input[type="checkbox"]:checked + label::before {
- opacity: 0;
-}
-
-.checkbox input[type="checkbox"]:disabled + label,
-.radio input[type="radio"]:disabled + label,
-.checkbox input[type="checkbox"]:disabled:checked + label::after {
- opacity: .7;
-}
-
-.checkbox input[type="checkbox"]:disabled + label::before,
-.checkbox input[type="checkbox"]:disabled + label::after {
- cursor: not-allowed;
-}
-
-.checkbox input[type="checkbox"]:disabled + label,
-.radio input[type="radio"]:disabled + label {
- cursor: not-allowed;
-}
-
-.checkbox.checkbox-circle label::before {
- border-radius: 50%;
-}
-
-.checkbox.checkbox-inline {
- padding-left: 0;
-}
-
-.checkbox-primary input[type="checkbox"]:checked + label::before {
- background-color: #428bca;
- border-color: #428bca;
-}
-
-.checkbox-primary input[type="checkbox"]:checked + label::after {
- color: #fff;
-}
-
-.checkbox-danger input[type="checkbox"]:checked + label::before {
- background-color: #d9534f;
- border-color: #d9534f;
-}
-
-.checkbox-danger input[type="checkbox"]:checked + label::after {
- color: #fff;
-}
-
-.checkbox-info input[type="checkbox"]:checked + label::before {
- background-color: #5bc0de;
- border-color: #5bc0de;
-}
-
-.checkbox-info input[type="checkbox"]:checked + label::after {
- color: #fff;
-}
-
-.checkbox-warning input[type="checkbox"]:checked + label::before {
- background-color: #f0ad4e;
- border-color: #f0ad4e;
-}
-
-.checkbox-warning input[type="checkbox"]:checked + label::after {
- color: #fff;
-}
-
-.checkbox-success input[type="checkbox"]:checked + label::before {
- background-color: #5cb85c;
- border-color: #5cb85c;
-}
-
-.checkbox-success input[type="checkbox"]:checked + label::after {
- color: #fff;
-}
-
-.radio label::before,
-.radio label::after {
- font-family: 'FontAwesome';
- content: "\f10c";
- font-size: 20px;
- height: 20px;
- width: 20px;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- display: inline-block;
- position: absolute;
- line-height: 19px;
- left: 0;
- top: 0;
- color: #DDDDDD;
- padding: 1px;
- transition: color 0.2s linear;
-}
-
-.radio input[type="radio"]:checked + label::after {
- font-family: 'FontAwesome';
- content: "\f192";
- color: #DDDDDD;
-}
-
-.radio input[type="radio"]:checked + label::after {
- color: #1DC7EA;
-}
-
-.radio input[type="radio"]:disabled + label,
-.radio input[type="radio"]:disabled + label::before,
-.radio input[type="radio"]:disabled + label::after {
- opacity: .5;
-}
-
-.radio.radio-inline {
- margin-top: 0;
-}
-
-/**
- * bootstrap-switch - Turn checkboxes and radio buttons into toggle switches.
- *
- * @version v3.3.4
- * @homepage https://bttstrp.github.io/bootstrap-switch
- * @author Mattia Larentis (http://larentis.eu)
- * @license Apache-2.0
- */
-.bootstrap-switch {
- display: inline-block;
- direction: ltr;
- cursor: pointer;
- border-radius: 4px;
- border: 1px solid;
- border-color: #ccc;
- position: relative;
- text-align: left;
- overflow: hidden;
- line-height: 8px;
- z-index: 0;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- vertical-align: middle;
- -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
- -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
- transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-}
-
-.bootstrap-switch .bootstrap-switch-container {
- display: inline-block;
- top: 0;
- border-radius: 4px;
- -webkit-transform: translate3d(0, 0, 0);
- transform: translate3d(0, 0, 0);
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on,
-.bootstrap-switch .bootstrap-switch-handle-off,
-.bootstrap-switch .bootstrap-switch-label {
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- cursor: pointer;
- display: table-cell;
- vertical-align: middle;
- padding: 6px 12px;
- font-size: 14px;
- line-height: 20px;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on,
-.bootstrap-switch .bootstrap-switch-handle-off {
- text-align: center;
- z-index: 1;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary {
- color: #fff;
- background: #337ab7;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info {
- color: #fff;
- background: #5bc0de;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success {
- color: #fff;
- background: #5cb85c;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning {
- background: #f0ad4e;
- color: #fff;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger {
- color: #fff;
- background: #d9534f;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,
-.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default {
- color: #000;
- background: #eeeeee;
-}
-
-.bootstrap-switch .bootstrap-switch-label {
- text-align: center;
- margin-top: -1px;
- margin-bottom: -1px;
- z-index: 100;
- color: #333;
- background: #fff;
-}
-
-.bootstrap-switch span::before {
- content: "\200b";
-}
-
-.bootstrap-switch .bootstrap-switch-handle-on {
- border-bottom-left-radius: 3px;
- border-top-left-radius: 3px;
-}
-
-.bootstrap-switch .bootstrap-switch-handle-off {
- border-bottom-right-radius: 3px;
- border-top-right-radius: 3px;
-}
-
-.bootstrap-switch input[type='radio'],
-.bootstrap-switch input[type='checkbox'] {
- position: absolute !important;
- top: 0;
- left: 0;
- margin: 0;
- z-index: -1;
- opacity: 0;
- filter: alpha(opacity=0);
- visibility: hidden;
-}
-
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label {
- padding: 1px 5px;
- font-size: 12px;
- line-height: 1.5;
-}
-
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label {
- padding: 5px 10px;
- font-size: 12px;
- line-height: 1.5;
-}
-
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label {
- padding: 6px 16px;
- font-size: 18px;
- line-height: 1.3333333;
-}
-
-.bootstrap-switch.bootstrap-switch-disabled,
-.bootstrap-switch.bootstrap-switch-readonly,
-.bootstrap-switch.bootstrap-switch-indeterminate {
- cursor: default !important;
-}
-
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,
-.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label {
- opacity: 0.5;
- filter: alpha(opacity=50);
- cursor: default !important;
-}
-
-.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container {
- -webkit-transition: margin-left 0.5s;
- -o-transition: margin-left 0.5s;
- transition: margin-left 0.5s;
-}
-
-.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on {
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- border-bottom-right-radius: 3px;
- border-top-right-radius: 3px;
-}
-
-.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off {
- border-bottom-right-radius: 0;
- border-top-right-radius: 0;
- border-bottom-left-radius: 3px;
- border-top-left-radius: 3px;
-}
-
-.bootstrap-switch.bootstrap-switch-focused {
- border-color: #66afe9;
- outline: 0;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-}
-
-.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label {
- border-bottom-right-radius: 3px;
- border-top-right-radius: 3px;
-}
-
-.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,
-.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label {
- border-bottom-left-radius: 3px;
- border-top-left-radius: 3px;
-}
-
-.nav > li > a:hover,
-.nav > li > a:focus {
- background-color: transparent;
-}
-
-.navbar {
- border: 0;
- font-size: 16px;
- border-radius: 0;
-}
-
-.navbar > .container .navbar-brand,
-.navbar > .container-fluid .navbar-brand {
- margin-left: 0;
-}
-
-.navbar .navbar-brand {
- font-weight: 400;
- margin: 5px 0px;
- padding: 15px 15px;
- font-size: 20px;
-}
-
-.navbar .navbar-nav > li > a {
- padding: 10px 15px;
- margin: 10px 3px;
- position: relative;
-}
-
-.navbar .navbar-nav > li > a.btn {
- margin: 15px 3px;
- padding: 8px 16px;
-}
-
-.navbar .navbar-nav > li > a.btn-round {
- margin: 16px 3px;
-}
-
-.navbar .navbar-nav > li > a [class^="fa"] {
- font-size: 19px;
- position: relative;
- line-height: 16px;
- top: 1px;
-}
-
-.navbar .navbar-nav .notification {
- position: absolute;
- background-color: #FB404B;
- text-align: center;
- border-radius: 10px;
- min-width: 18px;
- padding: 0 5px;
- height: 18px;
- font-size: 12px;
- color: #FFFFFF;
- font-weight: bold;
- line-height: 18px;
- top: 0px;
- left: 7px;
-}
-
-.navbar .btn {
- margin: 15px 3px;
- font-size: 14px;
-}
-
-.navbar .btn-simple {
- font-size: 16px;
-}
-
-.navbar.fixed {
- width: calc(100% - $sidebar-width);
- right: 0;
- left: auto;
- border-radius: 0;
-}
-
-.navbar-nav > li > .dropdown-menu {
- border-radius: 10px;
- margin-top: -5px;
-}
-
-.navbar-transparent .navbar-brand, [class*="navbar-ct"] .navbar-brand {
- color: #FFFFFF;
- opacity: 0.9;
- filter: alpha(opacity=90);
-}
-
-.navbar-transparent .navbar-brand:focus, .navbar-transparent .navbar-brand:hover, [class*="navbar-ct"] .navbar-brand:focus, [class*="navbar-ct"] .navbar-brand:hover {
- background-color: transparent;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.navbar-transparent .navbar-nav > li > a:not(.btn), [class*="navbar-ct"] .navbar-nav > li > a:not(.btn) {
- color: #FFFFFF;
- border-color: #FFFFFF;
- opacity: 0.8;
- filter: alpha(opacity=80);
-}
-
-.navbar-transparent .navbar-nav > .active > a:not(.btn),
-.navbar-transparent .navbar-nav > .active > a:hover:not(.btn),
-.navbar-transparent .navbar-nav > .active > a:focus:not(.btn),
-.navbar-transparent .navbar-nav > li > a:hover:not(.btn),
-.navbar-transparent .navbar-nav > li > a:focus:not(.btn), [class*="navbar-ct"] .navbar-nav > .active > a:not(.btn),
-[class*="navbar-ct"] .navbar-nav > .active > a:hover:not(.btn),
-[class*="navbar-ct"] .navbar-nav > .active > a:focus:not(.btn),
-[class*="navbar-ct"] .navbar-nav > li > a:hover:not(.btn),
-[class*="navbar-ct"] .navbar-nav > li > a:focus:not(.btn) {
- background-color: transparent;
- border-radius: 3px;
- color: #FFFFFF;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.navbar-transparent .navbar-nav .nav > li > a.btn:hover, [class*="navbar-ct"] .navbar-nav .nav > li > a.btn:hover {
- background-color: transparent;
-}
-
-.navbar-transparent .navbar-nav > .dropdown > a .caret,
-.navbar-transparent .navbar-nav > .dropdown > a:hover .caret,
-.navbar-transparent .navbar-nav > .dropdown > a:focus .caret, [class*="navbar-ct"] .navbar-nav > .dropdown > a .caret,
-[class*="navbar-ct"] .navbar-nav > .dropdown > a:hover .caret,
-[class*="navbar-ct"] .navbar-nav > .dropdown > a:focus .caret {
- border-bottom-color: #FFFFFF;
- border-top-color: #FFFFFF;
-}
-
-.navbar-transparent .navbar-nav > .open > a,
-.navbar-transparent .navbar-nav > .open > a:hover,
-.navbar-transparent .navbar-nav > .open > a:focus, [class*="navbar-ct"] .navbar-nav > .open > a,
-[class*="navbar-ct"] .navbar-nav > .open > a:hover,
-[class*="navbar-ct"] .navbar-nav > .open > a:focus {
- background-color: transparent;
- color: #FFFFFF;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.navbar-transparent .btn-default, [class*="navbar-ct"] .btn-default {
- color: #FFFFFF;
- border-color: #FFFFFF;
-}
-
-.navbar-transparent .btn-default.btn-fill, [class*="navbar-ct"] .btn-default.btn-fill {
- color: #9A9A9A;
- background-color: #FFFFFF;
- opacity: 0.9;
- filter: alpha(opacity=90);
-}
-
-.navbar-transparent .btn-default.btn-fill:hover,
-.navbar-transparent .btn-default.btn-fill:focus,
-.navbar-transparent .btn-default.btn-fill:active,
-.navbar-transparent .btn-default.btn-fill.active,
-.navbar-transparent .open .dropdown-toggle.btn-fill.btn-default, [class*="navbar-ct"] .btn-default.btn-fill:hover,
-[class*="navbar-ct"] .btn-default.btn-fill:focus,
-[class*="navbar-ct"] .btn-default.btn-fill:active,
-[class*="navbar-ct"] .btn-default.btn-fill.active,
-[class*="navbar-ct"] .open .dropdown-toggle.btn-fill.btn-default {
- border-color: #FFFFFF;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.navbar-transparent .dropdown-menu .divider {
- background-color: rgba(255, 255, 255, 0.2);
-}
-
-.nav-open .nav .caret {
- border-bottom-color: #FFFFFF;
- border-top-color: #FFFFFF;
-}
-
-.navbar-default {
- background-color: rgba(255, 255, 255, 0.96);
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.navbar-default .navbar-nav > li > a:not(.btn) {
- color: #9A9A9A;
-}
-
-.navbar-default .navbar-nav > .active > a,
-.navbar-default .navbar-nav > .active > a:not(.btn):hover,
-.navbar-default .navbar-nav > .active > a:not(.btn):focus,
-.navbar-default .navbar-nav > li > a:not(.btn):hover,
-.navbar-default .navbar-nav > li > a:not(.btn):focus {
- background-color: transparent;
- border-radius: 3px;
- color: #1DC7EA;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.navbar-default .navbar-nav > .dropdown > a:hover .caret,
-.navbar-default .navbar-nav > .dropdown > a:focus .caret {
- border-bottom-color: #1DC7EA;
- border-top-color: #1DC7EA;
-}
-
-.navbar-default .navbar-nav > .open > a,
-.navbar-default .navbar-nav > .open > a:hover,
-.navbar-default .navbar-nav > .open > a:focus {
- background-color: transparent;
- color: #1DC7EA;
-}
-
-.navbar-default .navbar-nav .navbar-toggle:hover, .navbar-default .navbar-nav .navbar-toggle:focus {
- background-color: transparent;
-}
-
-.navbar-default:not(.navbar-transparent) .btn-default:hover {
- color: #1DC7EA;
- border-color: #1DC7EA;
-}
-
-.navbar-default:not(.navbar-transparent) .btn-neutral,
-.navbar-default:not(.navbar-transparent) .btn-neutral:hover,
-.navbar-default:not(.navbar-transparent) .btn-neutral:active {
- color: #9A9A9A;
-}
-
-/* Navbar with icons */
-.navbar-icons.navbar .navbar-brand {
- margin-top: 12px;
- margin-bottom: 12px;
-}
-
-.navbar-icons .navbar-nav > li > a {
- text-align: center;
- padding: 6px 15px;
- margin: 6px 3px;
-}
-
-.navbar-icons .navbar-nav [class^="pe"] {
- font-size: 30px;
- position: relative;
-}
-
-.navbar-icons .navbar-nav p {
- margin: 3px 0 0;
-}
-
-.navbar-form {
- -webkit-box-shadow: none;
- box-shadow: none;
-}
-
-.navbar-form .form-control {
- border-radius: 0;
- border: 0;
- padding: 0;
- background-color: transparent;
- height: 22px;
- font-size: 16px;
- line-height: 1.5;
- color: #E3E3E3;
-}
-
-.navbar-transparent .navbar-form .form-control,
-[class*="navbar-ct"] .navbar-form .form-control {
- color: #FFFFFF;
- border: 0;
- border-bottom: 1px solid rgba(255, 255, 255, 0.6);
-}
-
-.navbar-ct-blue {
- background-color: #4091e2;
-}
-
-.navbar-ct-azure {
- background-color: #63d8f1;
-}
-
-.navbar-ct-green {
- background-color: #a1e82c;
-}
-
-.navbar-ct-orange {
- background-color: #ffbc67;
-}
-
-.navbar-ct-red {
- background-color: #fc727a;
-}
-
-.navbar-transparent {
- padding-top: 15px;
- background-color: transparent;
- border-bottom: 1px solid transparent;
-}
-
-.navbar-toggle {
- margin-top: 19px;
- margin-bottom: 19px;
- border: 0;
-}
-
-.navbar-toggle .icon-bar {
- background-color: #FFFFFF;
-}
-
-.navbar-toggle .navbar-collapse,
-.navbar-toggle .navbar-form {
- border-color: transparent;
-}
-
-.navbar-toggle.navbar-default .navbar-toggle:hover,
-.navbar-toggle.navbar-default .navbar-toggle:focus {
- background-color: transparent;
-}
-
-.footer {
- background-color: #FFFFFF;
- line-height: 20px;
-}
-
-.footer nav > ul {
- list-style: none;
- margin: 0;
- padding: 0;
- font-weight: normal;
-}
-
-.footer nav > ul a:not(.btn) {
- color: #9A9A9A;
- display: block;
- margin-bottom: 3px;
-}
-
-.footer nav > ul a:not(.btn):hover, .footer nav > ul a:not(.btn):focus {
- color: #777777;
-}
-
-.footer .social-area {
- padding: 15px 0;
-}
-
-.footer .social-area h5 {
- padding-bottom: 15px;
-}
-
-.footer .social-area > a:not(.btn) {
- color: #9A9A9A;
- display: inline-block;
- vertical-align: top;
- padding: 10px 5px;
- font-size: 20px;
- font-weight: normal;
- line-height: 20px;
- text-align: center;
-}
-
-.footer .social-area > a:not(.btn):hover, .footer .social-area > a:not(.btn):focus {
- color: #777777;
-}
-
-.footer .copyright {
- color: #777777;
- padding: 10px 15px;
- margin: 10px 3px;
- line-height: 20px;
- font-size: 14px;
-}
-
-.footer hr {
- border-color: #DDDDDD;
-}
-
-.footer .title {
- color: #777777;
-}
-
-.footer-default {
- background-color: #F5F5F5;
-}
-
-.footer:not(.footer-big) nav > ul {
- font-size: 14px;
-}
-
-.footer:not(.footer-big) nav > ul li {
- margin-left: 20px;
- float: left;
-}
-
-.footer:not(.footer-big) nav > ul a {
- padding: 10px 0px;
- margin: 10px 10px 10px 0px;
-}
-
-/*.dropdown-menu {
- visibility: hidden;
- margin: 0;
- padding: 0;
- border-radius: 10px;
- display: block;
- z-index: 9000;
- position: absolute;
- opacity: 0;
- filter: alpha(opacity=0);
- -webkit-box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.125);
- box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.125);
-}
-
-.open .dropdown-menu {
- opacity: 1;
- filter: alpha(opacity=100);
- visibility: visible;
-}
-
-.select .dropdown-menu {
- border-radius: 0 0 10px 10px;
- -webkit-box-shadow: none;
- box-shadow: none;
- -webkit-transform-origin: 50% -40px;
- -moz-transform-origin: 50% -40px;
- -o-transform-origin: 50% -40px;
- -ms-transform-origin: 50% -40px;
- transform-origin: 50% -40px;
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -o-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- -webkit-transition: all 150ms linear;
- -moz-transition: all 150ms linear;
- -o-transition: all 150ms linear;
- -ms-transition: all 150ms linear;
- transition: all 150ms linear;
- margin-top: -20px;
-}
-
-.select.open .dropdown-menu {
- margin-top: -1px;
-}
-
-.dropdown-menu > li > a {
- padding: 8px 16px;
- color: #333333;
-}
-
-.dropdown-menu > li > a img {
- margin-top: -3px;
-}
-
-.dropdown-menu > li > a:focus {
- outline: 0 !important;
-}
-
-.btn-group.select .dropdown-menu {
- min-width: 100%;
-}
-
-.dropdown-menu > li:first-child > a {
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
-}
-
-.dropdown-menu > li:last-child > a {
- border-bottom-left-radius: 10px;
- border-bottom-right-radius: 10px;
-}
-
-.select .dropdown-menu > li:first-child > a {
- border-radius: 0;
- border-bottom: 0 none;
-}
-
-.dropdown-menu > li > a:hover,
-.dropdown-menu > li > a:focus {
- background-color: #F5F5F5;
- color: #333333;
- opacity: 1;
- text-decoration: none;
-}
-
-.dropdown-menu.dropdown-blue > li > a:hover,
-.dropdown-menu.dropdown-blue > li > a:focus {
- background-color: rgba(52, 114, 247, 0.2);
-}
-
-.dropdown-menu.dropdown-azure > li > a:hover,
-.dropdown-menu.dropdown-azure > li > a:focus {
- background-color: rgba(29, 199, 234, 0.2);
-}
-
-.dropdown-menu.ct-green > li > a:hover,
-.dropdown-menu.ct-green > li > a:focus {
- background-color: rgba(135, 203, 22, 0.2);
-}
-
-.dropdown-menu.dropdown-orange > li > a:hover,
-.dropdown-menu.dropdown-orange > li > a:focus {
- background-color: rgba(255, 149, 0, 0.2);
-}
-
-.dropdown-menu.dropdown-red > li > a:hover,
-.dropdown-menu.dropdown-red > li > a:focus {
- background-color: rgba(255, 74, 85, 0.2);
-}*/
-
-.dropdown-with-icons > li > a {
- padding-left: 0px;
- line-height: 28px;
-}
-
-.dropdown-with-icons i {
- text-align: center;
- line-height: 28px;
- float: left;
-}
-
-.dropdown-with-icons i[class^="pe-"] {
- font-size: 24px;
- width: 46px;
-}
-
-.dropdown-with-icons i[class^="fa"] {
- font-size: 14px;
- width: 38px;
-}
-
-.btn-group.select {
- overflow: hidden;
-}
-
-.btn-group.select.open {
- overflow: visible;
-}
-
-.card {
- border-radius: 4px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(63, 63, 68, 0.1);
- background-color: #FFFFFF;
- margin-bottom: 30px;
-}
-
-.card .image {
- width: 100%;
- overflow: hidden;
- height: 260px;
- border-radius: 4px 4px 0 0;
- position: relative;
- -webkit-transform-style: preserve-3d;
- -moz-transform-style: preserve-3d;
- transform-style: preserve-3d;
-}
-
-.card .image img {
- width: 100%;
-}
-
-.card .filter {
- position: absolute;
- z-index: 2;
- background-color: rgba(0, 0, 0, 0.68);
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- text-align: center;
- opacity: 0;
- filter: alpha(opacity=0);
-}
-
-.card .filter .btn {
- position: relative;
- top: 50%;
- -webkit-transform: translateY(-50%);
- -ms-transform: translateY(-50%);
- transform: translateY(-50%);
-}
-
-.card:hover .filter {
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.card .btn-hover {
- opacity: 0;
- filter: alpha(opacity=0);
-}
-
-.card:hover .btn-hover {
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.card .content {
- padding: 15px 15px 10px 15px;
-}
-
-.card .header {
- padding: 15px 15px 0;
-}
-
-.card .category,
-.card label {
- font-size: 14px;
- font-weight: 400;
- color: #9A9A9A;
- margin-bottom: 0px;
-}
-
-.card .category i,
-.card label i {
- font-size: 16px;
-}
-
-.card label {
- font-size: 12px;
- margin-bottom: 5px;
- text-transform: uppercase;
-}
-
-.card .title {
- margin: 0;
- color: #333333;
- font-weight: 300;
-}
-
-.card .avatar {
- width: 30px;
- height: 30px;
- overflow: hidden;
- border-radius: 50%;
- margin-right: 5px;
-}
-
-.card .description {
- font-size: 14px;
- color: #333;
-}
-
-.card .footer {
- padding: 0;
- background-color: transparent;
- line-height: 30px;
-}
-
-.card .footer .legend {
- padding: 5px 0;
-}
-
-.card .footer hr {
- margin-top: 5px;
- margin-bottom: 5px;
-}
-
-.card .stats {
- color: #a9a9a9;
-}
-
-.card .footer div {
- display: inline-block;
-}
-
-.card .author {
- font-size: 12px;
- font-weight: 600;
- text-transform: uppercase;
-}
-
-.card .author i {
- font-size: 14px;
-}
-
-.card h6 {
- font-size: 12px;
- margin: 0;
-}
-
-.card.card-separator:after {
- height: 100%;
- right: -15px;
- top: 0;
- width: 1px;
- background-color: #DDDDDD;
- content: "";
- position: absolute;
-}
-
-.card .ct-chart {
- margin: 30px 0 30px;
- height: 245px;
-}
-
-.card .table tbody td:first-child,
-.card .table thead th:first-child {
- padding-left: 15px;
-}
-
-.card .table tbody td:last-child,
-.card .table thead th:last-child {
- padding-right: 15px;
-}
-
-.card .alert {
- border-radius: 4px;
- position: relative;
-}
-
-.card .alert.alert-with-icon {
- padding-left: 65px;
-}
-
-.card-user .image {
- height: 110px;
-}
-
-.card-user .image-plain {
- height: 0;
- margin-top: 110px;
-}
-
-.card-user .author {
- text-align: center;
- text-transform: none;
- margin-top: -70px;
-}
-
-.card-user .avatar {
- width: 124px;
- height: 124px;
- border: 5px solid #FFFFFF;
- position: relative;
- margin-bottom: 15px;
-}
-
-.card-user .avatar.border-gray {
- border-color: #EEEEEE;
-}
-
-.card-user .title {
- line-height: 24px;
-}
-
-.card-user .content {
- min-height: 240px;
-}
-
-.card-user .footer,
-.card-price .footer {
- padding: 5px 15px 10px;
-}
-
-.card-user hr,
-.card-price hr {
- margin: 5px 15px;
-}
-
-.card-plain {
- background-color: transparent;
- box-shadow: none;
- border-radius: 0;
-}
-
-.card-plain .image {
- border-radius: 4px;
-}
-
-.ct-label {
- fill: rgba(0, 0, 0, 0.4);
- color: rgba(0, 0, 0, 0.4);
- font-size: 1.3rem;
- line-height: 1;
-}
-
-.ct-chart-line .ct-label,
-.ct-chart-bar .ct-label {
- display: block;
- display: -webkit-box;
- display: -moz-box;
- display: -ms-flexbox;
- display: -webkit-flex;
- display: flex;
-}
-
-.ct-label.ct-horizontal.ct-start {
- -webkit-box-align: flex-end;
- -webkit-align-items: flex-end;
- -ms-flex-align: flex-end;
- align-items: flex-end;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: start;
-}
-
-.ct-label.ct-horizontal.ct-end {
- -webkit-box-align: flex-start;
- -webkit-align-items: flex-start;
- -ms-flex-align: flex-start;
- align-items: flex-start;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: start;
-}
-
-.ct-label.ct-vertical.ct-start {
- -webkit-box-align: flex-end;
- -webkit-align-items: flex-end;
- -ms-flex-align: flex-end;
- align-items: flex-end;
- -webkit-box-pack: flex-end;
- -webkit-justify-content: flex-end;
- -ms-flex-pack: flex-end;
- justify-content: flex-end;
- text-align: right;
- text-anchor: end;
-}
-
-.ct-label.ct-vertical.ct-end {
- -webkit-box-align: flex-end;
- -webkit-align-items: flex-end;
- -ms-flex-align: flex-end;
- align-items: flex-end;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: start;
-}
-
-.ct-chart-bar .ct-label.ct-horizontal.ct-start {
- -webkit-box-align: flex-end;
- -webkit-align-items: flex-end;
- -ms-flex-align: flex-end;
- align-items: flex-end;
- -webkit-box-pack: center;
- -webkit-justify-content: center;
- -ms-flex-pack: center;
- justify-content: center;
- text-align: center;
- text-anchor: start;
-}
-
-.ct-chart-bar .ct-label.ct-horizontal.ct-end {
- -webkit-box-align: flex-start;
- -webkit-align-items: flex-start;
- -ms-flex-align: flex-start;
- align-items: flex-start;
- -webkit-box-pack: center;
- -webkit-justify-content: center;
- -ms-flex-pack: center;
- justify-content: center;
- text-align: center;
- text-anchor: start;
-}
-
-.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start {
- -webkit-box-align: flex-end;
- -webkit-align-items: flex-end;
- -ms-flex-align: flex-end;
- align-items: flex-end;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: start;
-}
-
-.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end {
- -webkit-box-align: flex-start;
- -webkit-align-items: flex-start;
- -ms-flex-align: flex-start;
- align-items: flex-start;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: start;
-}
-
-.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start {
- -webkit-box-align: center;
- -webkit-align-items: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: flex-end;
- -webkit-justify-content: flex-end;
- -ms-flex-pack: flex-end;
- justify-content: flex-end;
- text-align: right;
- text-anchor: end;
-}
-
-.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end {
- -webkit-box-align: center;
- -webkit-align-items: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: flex-start;
- -webkit-justify-content: flex-start;
- -ms-flex-pack: flex-start;
- justify-content: flex-start;
- text-align: left;
- text-anchor: end;
-}
-
-.ct-grid {
- stroke: rgba(0, 0, 0, 0.2);
- stroke-width: 1px;
- stroke-dasharray: 2px;
-}
-
-.ct-point {
- stroke-width: 8px;
- stroke-linecap: round;
-}
-
-.ct-line {
- fill: none;
- stroke-width: 3px;
-}
-
-.ct-area {
- stroke: none;
- fill-opacity: 0.8;
-}
-
-.ct-bar {
- fill: none;
- stroke-width: 10px;
-}
-
-.ct-slice-donut {
- fill: none;
- stroke-width: 60px;
-}
-
-.ct-series-a .ct-point, .ct-series-a .ct-line, .ct-series-a .ct-bar, .ct-series-a .ct-slice-donut {
- stroke: #1DC7EA;
-}
-
-.ct-series-a .ct-slice-pie, .ct-series-a .ct-area {
- fill: #1DC7EA;
-}
-
-.ct-series-b .ct-point, .ct-series-b .ct-line, .ct-series-b .ct-bar, .ct-series-b .ct-slice-donut {
- stroke: #FB404B;
-}
-
-.ct-series-b .ct-slice-pie, .ct-series-b .ct-area {
- fill: #FB404B;
-}
-
-.ct-series-c .ct-point, .ct-series-c .ct-line, .ct-series-c .ct-bar, .ct-series-c .ct-slice-donut {
- stroke: #FFA534;
-}
-
-.ct-series-c .ct-slice-pie, .ct-series-c .ct-area {
- fill: #FFA534;
-}
-
-.ct-series-d .ct-point, .ct-series-d .ct-line, .ct-series-d .ct-bar, .ct-series-d .ct-slice-donut {
- stroke: #9368E9;
-}
-
-.ct-series-d .ct-slice-pie, .ct-series-d .ct-area {
- fill: #9368E9;
-}
-
-.ct-series-e .ct-point, .ct-series-e .ct-line, .ct-series-e .ct-bar, .ct-series-e .ct-slice-donut {
- stroke: #87CB16;
-}
-
-.ct-series-e .ct-slice-pie, .ct-series-e .ct-area {
- fill: #87CB16;
-}
-
-.ct-series-f .ct-point, .ct-series-f .ct-line, .ct-series-f .ct-bar, .ct-series-f .ct-slice-donut {
- stroke: #1F77D0;
-}
-
-.ct-series-f .ct-slice-pie, .ct-series-f .ct-area {
- fill: #1F77D0;
-}
-
-.ct-series-g .ct-point, .ct-series-g .ct-line, .ct-series-g .ct-bar, .ct-series-g .ct-slice-donut {
- stroke: #5e5e5e;
-}
-
-.ct-series-g .ct-slice-pie, .ct-series-g .ct-area {
- fill: #5e5e5e;
-}
-
-.ct-series-h .ct-point, .ct-series-h .ct-line, .ct-series-h .ct-bar, .ct-series-h .ct-slice-donut {
- stroke: #dd4b39;
-}
-
-.ct-series-h .ct-slice-pie, .ct-series-h .ct-area {
- fill: #dd4b39;
-}
-
-.ct-series-i .ct-point, .ct-series-i .ct-line, .ct-series-i .ct-bar, .ct-series-i .ct-slice-donut {
- stroke: #35465c;
-}
-
-.ct-series-i .ct-slice-pie, .ct-series-i .ct-area {
- fill: #35465c;
-}
-
-.ct-series-j .ct-point, .ct-series-j .ct-line, .ct-series-j .ct-bar, .ct-series-j .ct-slice-donut {
- stroke: #e52d27;
-}
-
-.ct-series-j .ct-slice-pie, .ct-series-j .ct-area {
- fill: #e52d27;
-}
-
-.ct-series-k .ct-point, .ct-series-k .ct-line, .ct-series-k .ct-bar, .ct-series-k .ct-slice-donut {
- stroke: #55acee;
-}
-
-.ct-series-k .ct-slice-pie, .ct-series-k .ct-area {
- fill: #55acee;
-}
-
-.ct-series-l .ct-point, .ct-series-l .ct-line, .ct-series-l .ct-bar, .ct-series-l .ct-slice-donut {
- stroke: #cc2127;
-}
-
-.ct-series-l .ct-slice-pie, .ct-series-l .ct-area {
- fill: #cc2127;
-}
-
-.ct-series-m .ct-point, .ct-series-m .ct-line, .ct-series-m .ct-bar, .ct-series-m .ct-slice-donut {
- stroke: #1769ff;
-}
-
-.ct-series-m .ct-slice-pie, .ct-series-m .ct-area {
- fill: #1769ff;
-}
-
-.ct-series-n .ct-point, .ct-series-n .ct-line, .ct-series-n .ct-bar, .ct-series-n .ct-slice-donut {
- stroke: #6188e2;
-}
-
-.ct-series-n .ct-slice-pie, .ct-series-n .ct-area {
- fill: #6188e2;
-}
-
-.ct-series-o .ct-point, .ct-series-o .ct-line, .ct-series-o .ct-bar, .ct-series-o .ct-slice-donut {
- stroke: #a748ca;
-}
-
-.ct-series-o .ct-slice-pie, .ct-series-o .ct-area {
- fill: #a748ca;
-}
-
-.ct-square {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-square:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 100%;
-}
-
-.ct-square:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-square > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-minor-second {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-minor-second:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 93.75%;
-}
-
-.ct-minor-second:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-minor-second > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-second {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-second:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 88.88889%;
-}
-
-.ct-major-second:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-second > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-minor-third {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-minor-third:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 83.33333%;
-}
-
-.ct-minor-third:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-minor-third > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-third {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-third:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 80%;
-}
-
-.ct-major-third:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-third > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-perfect-fourth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-perfect-fourth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 75%;
-}
-
-.ct-perfect-fourth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-perfect-fourth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-perfect-fifth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-perfect-fifth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 66.66667%;
-}
-
-.ct-perfect-fifth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-perfect-fifth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-minor-sixth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-minor-sixth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 62.5%;
-}
-
-.ct-minor-sixth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-minor-sixth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-golden-section {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-golden-section:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 61.8047%;
-}
-
-.ct-golden-section:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-golden-section > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-sixth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-sixth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 60%;
-}
-
-.ct-major-sixth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-sixth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-minor-seventh {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-minor-seventh:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 56.25%;
-}
-
-.ct-minor-seventh:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-minor-seventh > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-seventh {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-seventh:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 53.33333%;
-}
-
-.ct-major-seventh:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-seventh > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-octave {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-octave:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 50%;
-}
-
-.ct-octave:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-octave > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-tenth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-tenth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 40%;
-}
-
-.ct-major-tenth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-tenth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-eleventh {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-eleventh:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 37.5%;
-}
-
-.ct-major-eleventh:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-eleventh > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-major-twelfth {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-major-twelfth:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 33.33333%;
-}
-
-.ct-major-twelfth:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-major-twelfth > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.ct-double-octave {
- display: block;
- position: relative;
- width: 100%;
-}
-
-.ct-double-octave:before {
- display: block;
- float: left;
- content: "";
- width: 0;
- height: 0;
- padding-bottom: 25%;
-}
-
-.ct-double-octave:after {
- content: "";
- display: table;
- clear: both;
-}
-
-.ct-double-octave > svg {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-@media (min-width: 992px) {
- .navbar-form {
- margin-top: 21px;
- margin-bottom: 21px;
- padding-left: 5px;
- padding-right: 5px;
- }
- .navbar-nav > li > .dropdown-menu, .dropdown .dropdown-menu {
- -webkit-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
- -moz-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
- -o-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
- -ms-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
- transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
- }
- .navbar-nav > li.open > .dropdown-menu, .dropdown.open .dropdown-menu {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -o-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- -webkit-transform-origin: 29px -50px;
- -moz-transform-origin: 29px -50px;
- -o-transform-origin: 29px -50px;
- -ms-transform-origin: 29px -50px;
- transform-origin: 29px -50px;
- }
- .sidebar .nav-mobile-menu {
- display: none;
- }
- .footer nav {
- margin-left: 15px;
- }
- .navbar-nav > li > .dropdown-menu:before {
- border-bottom: 11px solid rgba(0, 0, 0, 0.2);
- border-left: 11px solid transparent;
- border-right: 11px solid transparent;
- content: "";
- display: inline-block;
- position: absolute;
- left: 12px;
- top: -11px;
- }
- .navbar-nav > li > .dropdown-menu:after {
- border-bottom: 11px solid #FFFFFF;
- border-left: 11px solid transparent;
- border-right: 11px solid transparent;
- content: "";
- display: inline-block;
- position: absolute;
- left: 12px;
- top: -10px;
- }
- .navbar-nav.navbar-right > li > .dropdown-menu:before {
- left: auto;
- right: 12px;
- }
- .navbar-nav.navbar-right > li > .dropdown-menu:after {
- left: auto;
- right: 12px;
- }
- .footer:not(.footer-big) nav > ul li:first-child {
- margin-left: 0;
- }
- body > .navbar-collapse.collapse {
- display: none !important;
- }
- .card form [class*="col-"] {
- padding: 6px;
- }
- .card form [class*="col-"]:first-child {
- padding-left: 15px;
- }
- .card form [class*="col-"]:last-child {
- padding-right: 15px;
- }
-}
-
-/* Changes for small display */
-@media (max-width: 991px) {
- .main-panel {
- width: 100%;
- }
- .navbar-transparent {
- padding-top: 15px;
- background-color: rgba(0, 0, 0, 0.45);
- }
- body {
- position: relative;
- }
- .main-panel {
- -webkit-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -moz-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -o-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -ms-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- left: 0;
- }
- .navbar .container {
- left: 0;
- width: 100%;
- -webkit-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -moz-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -o-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -ms-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- position: relative;
- }
- .navbar .navbar-collapse.collapse,
- .navbar .navbar-collapse.collapse.in,
- .navbar .navbar-collapse.collapsing {
- display: none !important;
- }
- .navbar-nav > li {
- float: none;
- position: relative;
- display: block;
- }
- .navbar-collapse,
- .sidebar {
- position: fixed;
- display: block;
- top: 0;
- height: 100%;
- right: 0;
- left: auto;
- z-index: 1032;
- visibility: visible;
- background-color: #999;
- overflow-y: visible;
- border-top: none;
- text-align: left;
- padding: 0;
- -webkit-transform: translate3d(260px, 0, 0);
- -moz-transform: translate3d(260px, 0, 0);
- -o-transform: translate3d(260px, 0, 0);
- -ms-transform: translate3d(260px, 0, 0);
- transform: translate3d(260px, 0, 0);
- -webkit-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -moz-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -o-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- -ms-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
- }
- .navbar-collapse > ul,
- .sidebar > ul {
- position: relative;
- z-index: 4;
- overflow-y: scroll;
- height: calc(100vh - 61px);
- width: 100%;
- }
- .navbar-collapse::before,
- .sidebar::before {
- top: 0;
- left: 0;
- height: 100%;
- width: 100%;
- position: absolute;
- background-color: #282828;
- display: block;
- content: "";
- z-index: 1;
- }
- .navbar-collapse .logo,
- .sidebar .logo {
- position: relative;
- z-index: 4;
- }
- .navbar-collapse .nav li > a,
- .sidebar .nav li > a {
- padding: 10px 15px;
- }
- .navbar-collapse .nav,
- .sidebar .nav {
- margin-top: 10px;
- }
- .nav-open .navbar-collapse,
- .nav-open .sidebar {
- -webkit-transform: translate3d(0px, 0, 0);
- -moz-transform: translate3d(0px, 0, 0);
- -o-transform: translate3d(0px, 0, 0);
- -ms-transform: translate3d(0px, 0, 0);
- transform: translate3d(0px, 0, 0);
- }
- .nav-open .navbar .container {
- left: -250px;
- }
- .nav-open .main-panel {
- left: 0;
- -webkit-transform: translate3d(-260px, 0, 0);
- -moz-transform: translate3d(-260px, 0, 0);
- -o-transform: translate3d(-260px, 0, 0);
- -ms-transform: translate3d(-260px, 0, 0);
- transform: translate3d(-260px, 0, 0);
- }
- .nav-open .menu-on-left .sidebar {
- -webkit-transform: translate3d(0px, 0, 0);
- -moz-transform: translate3d(0px, 0, 0);
- -o-transform: translate3d(0px, 0, 0);
- -ms-transform: translate3d(0px, 0, 0);
- transform: translate3d(0px, 0, 0);
- }
- .nav-open .menu-on-left .main-panel {
- left: 0;
- -webkit-transform: translate3d(260px, 0, 0);
- -moz-transform: translate3d(260px, 0, 0);
- -o-transform: translate3d(260px, 0, 0);
- -ms-transform: translate3d(260px, 0, 0);
- transform: translate3d(260px, 0, 0);
- }
- .menu-on-left .sidebar {
- left: 0;
- right: auto;
- -webkit-transform: translate3d(-260px, 0, 0);
- -moz-transform: translate3d(-260px, 0, 0);
- -o-transform: translate3d(-260px, 0, 0);
- -ms-transform: translate3d(-260px, 0, 0);
- transform: translate3d(-260px, 0, 0);
- }
- .menu-on-left #bodyClick {
- right: 0;
- left: auto;
- }
- .navbar-toggle .icon-bar {
- display: block;
- position: relative;
- background: #fff;
- width: 24px;
- height: 2px;
- border-radius: 1px;
- margin: 0 auto;
- }
- .navbar-header .navbar-toggle {
- margin: 10px 15px 10px 0;
- width: 40px;
- height: 40px;
- }
- .bar1,
- .bar2,
- .bar3 {
- outline: 1px solid transparent;
- }
- .bar1 {
- top: 0px;
- -webkit-animation: topbar-back 500ms linear 0s;
- -moz-animation: topbar-back 500ms linear 0s;
- animation: topbar-back 500ms 0s;
- -webkit-animation-fill-mode: forwards;
- -moz-animation-fill-mode: forwards;
- animation-fill-mode: forwards;
- }
- .bar2 {
- opacity: 1;
- }
- .bar3 {
- bottom: 0px;
- -webkit-animation: bottombar-back 500ms linear 0s;
- -moz-animation: bottombar-back 500ms linear 0s;
- animation: bottombar-back 500ms 0s;
- -webkit-animation-fill-mode: forwards;
- -moz-animation-fill-mode: forwards;
- animation-fill-mode: forwards;
- }
- .toggled .bar1 {
- top: 6px;
- -webkit-animation: topbar-x 500ms linear 0s;
- -moz-animation: topbar-x 500ms linear 0s;
- animation: topbar-x 500ms 0s;
- -webkit-animation-fill-mode: forwards;
- -moz-animation-fill-mode: forwards;
- animation-fill-mode: forwards;
- }
- .toggled .bar2 {
- opacity: 0;
- }
- .toggled .bar3 {
- bottom: 6px;
- -webkit-animation: bottombar-x 500ms linear 0s;
- -moz-animation: bottombar-x 500ms linear 0s;
- animation: bottombar-x 500ms 0s;
- -webkit-animation-fill-mode: forwards;
- -moz-animation-fill-mode: forwards;
- animation-fill-mode: forwards;
- }
- @keyframes topbar-x {
- 0% {
- top: 0px;
- transform: rotate(0deg);
- }
- 45% {
- top: 6px;
- transform: rotate(145deg);
- }
- 75% {
- transform: rotate(130deg);
- }
- 100% {
- transform: rotate(135deg);
- }
- }
- @-webkit-keyframes topbar-x {
- 0% {
- top: 0px;
- -webkit-transform: rotate(0deg);
- }
- 45% {
- top: 6px;
- -webkit-transform: rotate(145deg);
- }
- 75% {
- -webkit-transform: rotate(130deg);
- }
- 100% {
- -webkit-transform: rotate(135deg);
- }
- }
- @-moz-keyframes topbar-x {
- 0% {
- top: 0px;
- -moz-transform: rotate(0deg);
- }
- 45% {
- top: 6px;
- -moz-transform: rotate(145deg);
- }
- 75% {
- -moz-transform: rotate(130deg);
- }
- 100% {
- -moz-transform: rotate(135deg);
- }
- }
- @keyframes topbar-back {
- 0% {
- top: 6px;
- transform: rotate(135deg);
- }
- 45% {
- transform: rotate(-10deg);
- }
- 75% {
- transform: rotate(5deg);
- }
- 100% {
- top: 0px;
- transform: rotate(0);
- }
- }
- @-webkit-keyframes topbar-back {
- 0% {
- top: 6px;
- -webkit-transform: rotate(135deg);
- }
- 45% {
- -webkit-transform: rotate(-10deg);
- }
- 75% {
- -webkit-transform: rotate(5deg);
- }
- 100% {
- top: 0px;
- -webkit-transform: rotate(0);
- }
- }
- @-moz-keyframes topbar-back {
- 0% {
- top: 6px;
- -moz-transform: rotate(135deg);
- }
- 45% {
- -moz-transform: rotate(-10deg);
- }
- 75% {
- -moz-transform: rotate(5deg);
- }
- 100% {
- top: 0px;
- -moz-transform: rotate(0);
- }
- }
- @keyframes bottombar-x {
- 0% {
- bottom: 0px;
- transform: rotate(0deg);
- }
- 45% {
- bottom: 6px;
- transform: rotate(-145deg);
- }
- 75% {
- transform: rotate(-130deg);
- }
- 100% {
- transform: rotate(-135deg);
- }
- }
- @-webkit-keyframes bottombar-x {
- 0% {
- bottom: 0px;
- -webkit-transform: rotate(0deg);
- }
- 45% {
- bottom: 6px;
- -webkit-transform: rotate(-145deg);
- }
- 75% {
- -webkit-transform: rotate(-130deg);
- }
- 100% {
- -webkit-transform: rotate(-135deg);
- }
- }
- @-moz-keyframes bottombar-x {
- 0% {
- bottom: 0px;
- -moz-transform: rotate(0deg);
- }
- 45% {
- bottom: 6px;
- -moz-transform: rotate(-145deg);
- }
- 75% {
- -moz-transform: rotate(-130deg);
- }
- 100% {
- -moz-transform: rotate(-135deg);
- }
- }
- @keyframes bottombar-back {
- 0% {
- bottom: 6px;
- transform: rotate(-135deg);
- }
- 45% {
- transform: rotate(10deg);
- }
- 75% {
- transform: rotate(-5deg);
- }
- 100% {
- bottom: 0px;
- transform: rotate(0);
- }
- }
- @-webkit-keyframes bottombar-back {
- 0% {
- bottom: 6px;
- -webkit-transform: rotate(-135deg);
- }
- 45% {
- -webkit-transform: rotate(10deg);
- }
- 75% {
- -webkit-transform: rotate(-5deg);
- }
- 100% {
- bottom: 0px;
- -webkit-transform: rotate(0);
- }
- }
- @-moz-keyframes bottombar-back {
- 0% {
- bottom: 6px;
- -moz-transform: rotate(-135deg);
- }
- 45% {
- -moz-transform: rotate(10deg);
- }
- 75% {
- -moz-transform: rotate(-5deg);
- }
- 100% {
- bottom: 0px;
- -moz-transform: rotate(0);
- }
- }
- @-webkit-keyframes fadeIn {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
- @-moz-keyframes fadeIn {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
- @keyframes fadeIn {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
- .dropdown-menu .divider {
- background-color: rgba(229, 229, 229, 0.15);
- }
- .navbar-nav {
- margin: 1px 0;
- float: none !important;
- }
- .navbar-nav .open .dropdown-menu > li > a {
- padding: 10px 15px 10px 60px;
- border-radius: 4px;
- color: inherit;
- }
- .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-nav .open .dropdown-menu > li > a:focus {
- background-color: transparent;
- }
- [class*="navbar-"] .navbar-nav > li > a,
- [class*="navbar-"] .navbar-nav > li > a:hover,
- [class*="navbar-"] .navbar-nav > li > a:focus,
- [class*="navbar-"] .navbar-nav .active > a,
- [class*="navbar-"] .navbar-nav .active > a:hover,
- [class*="navbar-"] .navbar-nav .active > a:focus,
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a,
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a:hover,
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a:focus,
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a:active {
- color: white;
- }
- [class*="navbar-"] .navbar-nav > li > a,
- [class*="navbar-"] .navbar-nav > li > a:hover,
- [class*="navbar-"] .navbar-nav > li > a:focus {
- opacity: .7;
- background-color: transparent;
- outline: none;
- }
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a:hover,
- [class*="navbar-"] .navbar-nav .open .dropdown-menu > li > a:focus {
- background-color: rgba(255, 255, 255, 0.1);
- }
- [class*="navbar-"] .navbar-nav.navbar-nav .open .dropdown-menu > li > a:active {
- opacity: 1;
- }
- [class*="navbar-"] .navbar-nav .dropdown > a:hover .caret {
- border-bottom-color: #fff;
- border-top-color: #fff;
- }
- [class*="navbar-"] .navbar-nav .dropdown > a:active .caret {
- border-bottom-color: white;
- border-top-color: white;
- }
- .dropdown-menu {
- display: none;
- }
- .navbar-fixed-top {
- -webkit-backface-visibility: hidden;
- }
- #bodyClick {
- height: 100%;
- width: calc(100% - 260px);
- position: fixed;
- opacity: 0;
- top: 0;
- left: 0;
- content: "";
- z-index: 9999;
- overflow-x: hidden;
- }
- .social-line .btn {
- margin: 0 0 10px 0;
- }
- .subscribe-line .form-control {
- margin: 0 0 10px 0;
- }
- .social-line.pull-right {
- float: none;
- }
- .footer nav.pull-left {
- float: none !important;
- }
- .footer:not(.footer-big) nav > ul li {
- float: none;
- }
- .social-area.pull-right {
- float: none !important;
- }
- .form-control + .form-control-feedback {
- margin-top: -8px;
- }
- .navbar-toggle:hover, .navbar-toggle:focus {
- background-color: transparent !important;
- }
- .btn.dropdown-toggle {
- margin-bottom: 0;
- }
- .media-post .author {
- width: 20%;
- float: none !important;
- display: block;
- margin: 0 auto 10px;
- }
- .media-post .media-body {
- width: 100%;
- }
- .navbar-collapse.collapse {
- height: 100% !important;
- }
- .navbar-collapse.collapse.in {
- display: block;
- }
- .navbar-header .collapse, .navbar-toggle {
- display: block !important;
- }
- .navbar-header {
- float: none;
- }
- .navbar-nav .open .dropdown-menu {
- position: static;
- float: none;
- width: auto;
- margin-top: 0;
- background-color: transparent;
- border: 0;
- -webkit-box-shadow: none;
- box-shadow: none;
- }
- .navbar-collapse .nav p {
- font-size: 14px;
- margin: 0;
- }
- .navbar-collapse [class^="pe-7s-"] {
- float: left;
- font-size: 20px;
- margin-right: 10px;
- }
-}
-
-@media (min-width: 992px) {
- .table-full-width {
- margin-left: -15px;
- margin-right: -15px;
- }
- .table-responsive {
- overflow: visible;
- }
- .navbar-nav p {
- line-height: normal;
- margin: 0;
- }
-}
-
-@media (max-width: 991px) {
- .table-responsive {
- width: 100%;
- margin-bottom: 15px;
- overflow-x: scroll;
- overflow-y: hidden;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- -webkit-overflow-scrolling: touch;
- }
-}
diff --git a/public/static/css/style.css b/public/static/css/style.css
deleted file mode 100644
index 2f0db36..0000000
--- a/public/static/css/style.css
+++ /dev/null
@@ -1,498 +0,0 @@
-@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap');
-
-body, h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6, p, .navbar, .brand, .btn-simple, .alert, a, .td-name, td, button.close {
- font-family: 'Noto Sans', sans-serif !important;
- font-weight: 400 !important;
-}
-
-body {
- font-size: 1rem;
- font-weight: 400;
- line-height: 1.5;
- background-color: #121212 !important;
- color: white;
-}
-
-.card {
- background-color: #272727 !important;
-}
-
-.card-title {
- color: white;
-}
-
-html > ::-webkit-scrollbar {
- display: none;
-}
-
-.csidebar {
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 16.6%;
- background-color: #272727;
- float: left;
- background-size: cover;
- overflow-x: hidden !important;
-}
-
-.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-element > a {
- display: flex;
- align-items: center;
- width: 100%;
- color: white !important;
- font-size: 18px;
- margin-left: 4%;
-}
-
-.sidebar-text {
- margin-left: 4%;
- display: flex;
- align-items: center;
-}
-
-#sidebar-nav {
- flex: 1;
-}
-
-.sidebar-bottom {
- position: absolute !important;
- width: 100%;
-}
-
-.nav-link {
- color: white !important;
-}
-
-.super-container {
- display: flex;
-}
-
-.content {
- width: 100%;
-}
-
-.content-container {
- display: flex;
- width: 100%;
- margin: 4%;
-}
-
-.filterCard {
- cursor: pointer;
-}
-
-.table td, .table th {
-}
-
-.close-container {
- text-align: right;
- padding-bottom: 10px;
-}
-
-.input-fill {
- margin: 0 !important;
- padding: 0 !important;
-}
-
-.form-control, .input-group-text, .form-check-input {
- border-color: #2e3136 !important;
- background-color: #2e3136 !important;
- color: white !important;
-}
-
-.server {
- color: white;
-}
-
-#bg-dark {
- background-color: #272727 !important;
-}
-
-#sidebar-gradient:after {
- background: #272727 !important;
-}
-
-.simple-text {
- padding: 0 !important;
-}
-
-.white {
- color: white !important;
-}
-
-.icon {
- color: white;
- float: left;
- vertical-align: middle;
- padding-right: 5px;
-}
-
-.avatar {
- width: 32px;
- height: 32px;
- display: block;
- background-size: cover;
- border-radius: 50%;
- margin-left: 5px;
- float: right;
- vertical-align: middle;
- bottom: 100%;
- top: 0;
-}
-
-.toast-header {
- background-color: #272727 !important;
- color: white !important;
-}
-
-.toast-body {
- background-color: #2e3136 !important;
- color: white !important;
-}
-
-.toast {
- border-radius: .25rem !important;
- border-color: #272727 !important;
-}
-
-#premium-ad {
- display: none;
-}
-
-.tag-content {
- min-width: 300px;
- max-width: 300px;
- word-wrap: break-word;
-}
-
-.pointer {
- cursor: pointer;
-}
-
-.modal-content {
- color: white !important;
- background-color: #272727 !important;
-}
-
-.modal-footer, .modal-header {
- border-color: #2e3136 !important;
-}
-
-.vertical-center {
- margin: 0;
- position: absolute;
- top: 50%;
-}
-
-.fade-in {
- opacity: 1;
- animation-name: fadeInOpacity;
- animation-iteration-count: 1;
- animation-timing-function: ease-in;
- animation-duration: 0.5s;
-}
-
-@keyframes fadeInOpacity {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-.content {
- display: none;
-}
-
-/* drop down menu */
-.dropdown-menu {
- background-color: #2e3136 !important;
-}
-
-.btn-light:not(:disabled):not(.disabled).active, .btn-light:not(:disabled):not(.disabled):active, .show>.btn-light.dropdown-toggle {
- background-color: #2e3136 !important;
-}
-
-.bs-searchbox input {
- background-color: white !important;
- color: #2e3136 !important;
-}
-
-.dropdown-item > .text {
- color: white !important;
-}
-
-.dropdown-item:hover {
- background-color: #272727 !important;
-}
-
-.filter-option-inner-inner {
- color: white !important;
-}
-
-.dropdown-toggle {
- border-color: #2e3136 !important;
-}
-
-.bootstrap-select .bs-ok-default:after {
- color: #2ECC71 !important;
-}
-
-.wrapper {
- z-index: 1000 !important;
-}
-
-.guild {
- display: flex;
- align-items: center;
- box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
-
- width: 33%;
- background-color: #121212;
- height: 100px;
- margin-bottom: 10px;
- border-radius: 10px;
- cursor: pointer;
-}
-
-@media only screen and (max-width: 900px) {
- .guild {
- width: 100%;
- }
-}
-
-.guild-icon {
- height: 80px;
- background-color: #272727;
- border-radius: 50%;
- margin-left: 10px;
-}
-
-.guild-icon-fa {
- background-color: #272727;
- border-radius: 50%;
- margin-left: 10px;
- color: white;
- font-size: 60px !important;
- width: 80px;
- height: 80px;
- text-align: center;
- padding-top: 10px;
-}
-
-.guild-name {
- color: white !important;
- padding-left: 10px;
-}
-
-.flex-container {
- display: flex;
- width: 100%;
-}
-
-#guild-container {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: space-evenly;
-}
-
-#refresh-container {
- display: flex;
- justify-content: center;
-
- margin-top: 20px;
- color: white;
-}
-
-.tcontent-container {
- display: flex;
- justify-content: center;
- width: 100%;
-}
-
-.tcontent-container {
- display: none;
-}
-
-.team-card-container {
- display: flex;
- margin: 4% 0;
- min-height: 50%;
- width: 80%;
-}
-
-.tcard {
- display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- width: 100%;
-
- background-color: #272727 !important;
- border-radius: 5px;
- box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
-}
-
-.tcard-title {
- display: flex;
- align-items: center;
- width: 100%;
- border-bottom: 1px solid rgba(0,0,0,.125);
-}
-
-.tcard-title > span {
- color: white;
- font-size: 32px;
- font-weight: bold;
- margin: 2% 0 2% 2%;
-}
-
-.tcard-body {
- display: flex;
- flex-direction: column;
- padding: 2%;
- flex: 1;
- width: 100%;
- color: white;
-}
-
-.flex-center {
- display: flex;
- width: 100%;
- flex-direction: column;
- align-items: center;
-}
-
-.columns {
- display: flex;
- width: 100%;
- flex-direction: row;
- justify-content: space-between;
-}
-
-.column {
- display: flex;
- width: 100%;
- flex-direction: column;
- align-items: center;
-}
-
-.team-item {
- display: flex !important;
- justify-content: space-between;
- align-items: center;
- color: white !important;
-}
-
-.add-search {
- height: 100% !important;
- border-top-right-radius: 0 !important;
- border-bottom-right-radius: 0 !important;
-}
-
-.add-search-btn {
- border-top-left-radius: 0 !important;
- border-bottom-left-radius: 0 !important;
-}
-
-.inline {
- display: flex;
- width: 100%;
- flex-direction: row;
-}
-
-.trow {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
-}
-
-.search-dropdown, .search-dropdown:active, .search-dropdown:focus {
- width: 100%;
-
- background-color: #2e3136;
-
- border-top: 1px solid rgba(0,0,0,.125);
- border-left-color: #2e3136 !important;
- border-right-color: #2e3136 !important;
- border-bottom-color: #2e3136 !important;
- border-radius: 0 !important;
-
- scrollbar-color: dark;
- outline: none;
- overflow: auto;
-}
-
-.search-dropdown-item {
- font-size: 16px;
- color: white;
- padding-left: 12px;
-}
-
-.search-dropdown-item:active, .search-dropdown-item::selection {
- background-color: #272727;
-}
-
-.add-button {
- width: 100%;
- box-shadow: 0 10px 10px rgba(0, 0, 0, 0.25);
- border-top-left-radius: 0 !important;
- border-top-right-radius: 0 !important;
-}
-
-#user-dropdown-wrapper, #role-dropdown-wrapper {
- display: none;
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- z-index: 100;
-}
-
-.float-right {
- float: right;
-}
-
-.team-creation-form {
- display: flex;
- width: 100%;
- flex-direction: row;
-}
-
-.team-creation-name {
- display: flex;
- width: 80% !important;
- max-width: 320px;
- height: 100% !important;
-}
-
-.delete-team-icon {
- float: right;
- cursor: pointer;
- vertical-align: center;
-}
-
-#notificationmodal {
- z-index: 10000 !important;
-}
\ No newline at end of file
diff --git a/public/static/js/auth.js b/public/static/js/auth.js
deleted file mode 100644
index f4b9bbe..0000000
--- a/public/static/js/auth.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const _tokenKey = 'token';
-
-async function getToken() {
- let token = window.localStorage.getItem(_tokenKey);
- if (token == null) {
- let res = await axios.post('/token', undefined, {
- withCredentials: true,
- headers: {
- 'x-tickets': 'true'
- }
- });
-
- if (res.status !== 200 || !res.data.success) {
- console.log("An error occurred whilst retrieving an authentication token. Please contact the developer");
- console.log(res);
- return;
- }
-
- token = res.data.token;
- localStorage.setItem(_tokenKey, token);
- }
-
- return token;
-}
-
-function clearLocalStorage() {
- window.localStorage.clear();
-}
-
-async function setDefaultHeader() {
- axios.defaults.headers.common['Authorization'] = await getToken();
- axios.defaults.headers.common['x-tickets'] = 'true'; // arbitrary header name and value
- axios.defaults.validateStatus = false;
-}
-
-async function _refreshToken() {
- window.localStorage.removeItem(_tokenKey);
- await getToken();
-}
-
-function addRefreshInterceptor() {
- axios.interceptors.response.use(async (res) => { // we set validateStatus to false
- if (res.status === 401) {
- await _refreshToken();
- }
- return res;
- }, async (err) => {
- if (err.response.status === 401) {
- await _refreshToken();
- }
- return err.response;
- });
-}
-
-setDefaultHeader();
-addRefreshInterceptor();
\ No newline at end of file
diff --git a/public/static/js/loadingscreen.js b/public/static/js/loadingscreen.js
deleted file mode 100644
index 683d2e2..0000000
--- a/public/static/js/loadingscreen.js
+++ /dev/null
@@ -1,28 +0,0 @@
-function showLoadingScreen() {
- const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
- content.style.display = 'none';
- document.getElementById('loading-container').style.display = 'block';
-}
-
-function hideLoadingScreen() {
- document.getElementById('loading-container').style.display = 'none';
-
- const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
- if (content.classList.contains('tcontent-container')) {
- content.style.display = 'flex';
- } else {
- content.style.display = 'block';
- }
-
- content.classList.add('fade-in');
-}
-
-function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-
-async function withLoadingScreen(func) {
- showLoadingScreen();
- await func();
- hideLoadingScreen();
-}
diff --git a/public/static/js/modalbackdrop.js b/public/static/js/modalbackdrop.js
deleted file mode 100644
index 84e93d5..0000000
--- a/public/static/js/modalbackdrop.js
+++ /dev/null
@@ -1,23 +0,0 @@
-function clear(...elements) {
- for (const elementId of elements) {
- document.getElementById(elementId).value = '';
- }
-}
-
-function hideBackdrop() {
- for (const backdrop of document.getElementsByClassName('modal-backdrop fade show')) {
- backdrop.remove();
- }
-}
-
-function registerHideListener(elementId) {
- $(`#${elementId}`).on('hidden.bs.modal', hideBackdrop);
-}
-
-function showBackdrop() {
- hideBackdrop();
-
- const backdrop = document.createElement('div');
- backdrop.classList.add('modal-backdrop', 'fade', 'show');
- document.getElementsByClassName('main-panel')[0].appendChild(backdrop);
-}
diff --git a/public/static/js/utils.js b/public/static/js/utils.js
deleted file mode 100644
index f5f45b7..0000000
--- a/public/static/js/utils.js
+++ /dev/null
@@ -1,67 +0,0 @@
-function showToast(title, content) {
- const container = document.getElementById('toast-container');
-
- container.innerHTML += `
-
- `;
-
- $('.toast').toast('show');
-}
-
-function appendTd(tr, content) {
- const td = document.createElement('td');
- td.appendChild(document.createTextNode(content));
- td.classList.add('white');
- tr.appendChild(td);
- return td
-}
-
-function appendButton(tr, content, onclick, ...classList) {
- const tdRemove = document.createElement('td');
- const btn = document.createElement('button');
-
- btn.type = 'submit';
- btn.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto', ...classList);
- btn.appendChild(document.createTextNode(content));
- btn.onclick = onclick;
-
- tdRemove.appendChild(btn);
- tr.appendChild(tdRemove);
-}
-
-function appendButtonHref(tr, content, href) {
- const tdRemove = document.createElement('td');
- const btn = document.createElement('a');
-
- btn.href = href;
- btn.type = 'submit';
- btn.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
- btn.appendChild(document.createTextNode(content));
-
- tdRemove.appendChild(btn);
- tr.appendChild(tdRemove);
-}
-
-function prependChild(parent, child) {
- if (parent.children.length === 0) {
- parent.appendChild(child);
- } else {
- parent.insertBefore(child, parent.children[0]);
- }
-}
-
-function createElement(tag, ...classList) {
- const el = document.createElement(tag);
- el.classList.add(...classList);
- return el;
-}
\ No newline at end of file
diff --git a/public/templates/includes/head.tmpl b/public/templates/includes/head.tmpl
deleted file mode 100644
index f73c949..0000000
--- a/public/templates/includes/head.tmpl
+++ /dev/null
@@ -1,81 +0,0 @@
-{{define "head"}}
- Tickets | A Discord Support Manager Bot
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/loadingscreen.tmpl b/public/templates/includes/loadingscreen.tmpl
deleted file mode 100644
index 172e80b..0000000
--- a/public/templates/includes/loadingscreen.tmpl
+++ /dev/null
@@ -1,9 +0,0 @@
-{{define "loadingscreen"}}
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/multipaneleditmodal.tmpl b/public/templates/includes/multipaneleditmodal.tmpl
deleted file mode 100644
index 9453879..0000000
--- a/public/templates/includes/multipaneleditmodal.tmpl
+++ /dev/null
@@ -1,147 +0,0 @@
-{{define "multipaneleditmodal"}}
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/navbar.tmpl b/public/templates/includes/navbar.tmpl
deleted file mode 100644
index 6a767db..0000000
--- a/public/templates/includes/navbar.tmpl
+++ /dev/null
@@ -1,38 +0,0 @@
-{{define "navbar"}}
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/notifymodal.tmpl b/public/templates/includes/notifymodal.tmpl
deleted file mode 100644
index 96740db..0000000
--- a/public/templates/includes/notifymodal.tmpl
+++ /dev/null
@@ -1,56 +0,0 @@
-{{define "notifymodal"}}
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/paneleditmodal.tmpl b/public/templates/includes/paneleditmodal.tmpl
deleted file mode 100644
index a7b893c..0000000
--- a/public/templates/includes/paneleditmodal.tmpl
+++ /dev/null
@@ -1,235 +0,0 @@
-{{define "paneleditmodal"}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ticket Channel Category
-
-
-
-
-
-
-
-
-
-
-
- Expand advanced settings
-
-
-
-
-
-
-
-
-
-
-
- Welcome Message
-
-
-
-
-
-
-
- Mention On Open
-
-
-
-
-
-
- Support Teams
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/sidebar.tmpl b/public/templates/includes/sidebar.tmpl
deleted file mode 100644
index 31ead53..0000000
--- a/public/templates/includes/sidebar.tmpl
+++ /dev/null
@@ -1,40 +0,0 @@
-{{define "sidebar"}}
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/includes/substitutionmodal.tmpl b/public/templates/includes/substitutionmodal.tmpl
deleted file mode 100644
index 7214a35..0000000
--- a/public/templates/includes/substitutionmodal.tmpl
+++ /dev/null
@@ -1,43 +0,0 @@
-{{define "substitutions"}}
-
-
-
-
-
- %user%
- Mention the user
-
- %username%
- The user's name
-
- %ticket_id%
- The ticket's numeric ID
-
- %open_tickets%
- The amount of open tickets the server has
-
- %total_tickets%
- The amount of tickets that have ever been opened in the server
-
- %user_open_tickets%
- The amount of open tickets the user has in the server
-
- %ticket_limit%
- The per user ticket limit
-
- %channel%
- Mention the channel
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/layouts/error.tmpl b/public/templates/layouts/error.tmpl
deleted file mode 100644
index 1682b41..0000000
--- a/public/templates/layouts/error.tmpl
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
- {{template "head" .}}
-
-
-
-
-
diff --git a/public/templates/layouts/main.tmpl b/public/templates/layouts/main.tmpl
deleted file mode 100644
index 733d08a..0000000
--- a/public/templates/layouts/main.tmpl
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- {{template "head" .}}
-
-
-
- {{template "sidebar" .}}
-
- {{template "loadingscreen" .}}
-
- {{template "notifymodal" .}}
-
- {{template "content" .}}
-
-
-
-
-
diff --git a/public/templates/layouts/manage.tmpl b/public/templates/layouts/manage.tmpl
deleted file mode 100644
index 9e92e0b..0000000
--- a/public/templates/layouts/manage.tmpl
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- {{template "head" .}}
-
-
-
-
- {{template "navbar" .}}
- {{template "loadingscreen" .}}
-
-
- {{template "notifymodal" .}}
-
- {{template "content" .}}
-
-
-
-
diff --git a/public/templates/views/blacklist.tmpl b/public/templates/views/blacklist.tmpl
deleted file mode 100644
index e2b6555..0000000
--- a/public/templates/views/blacklist.tmpl
+++ /dev/null
@@ -1,141 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
-
-
- User ID
- Username#Discrim
- Remove
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/index.tmpl b/public/templates/views/index.tmpl
deleted file mode 100644
index 979717d..0000000
--- a/public/templates/views/index.tmpl
+++ /dev/null
@@ -1,120 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
- Invite to your server
-
-
-
-
-
- Refresh list
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/logs.tmpl b/public/templates/views/logs.tmpl
deleted file mode 100644
index 76dfd0f..0000000
--- a/public/templates/views/logs.tmpl
+++ /dev/null
@@ -1,186 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
-
- Ticket ID
- Username
- User ID
- Log URL
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/modmaillogs.tmpl b/public/templates/views/modmaillogs.tmpl
deleted file mode 100644
index 816c3bb..0000000
--- a/public/templates/views/modmaillogs.tmpl
+++ /dev/null
@@ -1,194 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
-
- Username
- User ID
- Archive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/panels.tmpl b/public/templates/views/panels.tmpl
deleted file mode 100644
index a586bd6..0000000
--- a/public/templates/views/panels.tmpl
+++ /dev/null
@@ -1,561 +0,0 @@
-{{define "content"}}
- {{template "substitutions" .}}
- {{template "paneleditmodal" .}}
- {{template "multipaneleditmodal" .}}
-
-
-
-
-
-
-
-
-
Your panel quota: /
-
Note: You can create unlimited panels with premium
-
-
-
-
- Channel
- Panel Title
- Ticket Channel Category
- Edit
- Delete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Embed Title
- Edit
- Delete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ticket Channel Category
-
-
-
-
-
-
-
-
-
-
-
-
-
- Expand advanced settings
-
-
-
-
-
-
-
-
-
-
-
- Welcome Message
-
-
-
-
-
-
-
-
-
- Mention On Open
-
-
-
-
-
-
- Support Teams
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/settings.tmpl b/public/templates/views/settings.tmpl
deleted file mode 100644
index 5de2d2d..0000000
--- a/public/templates/views/settings.tmpl
+++ /dev/null
@@ -1,511 +0,0 @@
-{{define "content"}}
-
- {{template "substitutions" .}}
-
-
-
-
-
-
-
-
-
-
-
-
- Prefix (Max len. 8)
-
-
-
-
-
-
- Ticket Limit
-
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
-
-
-
-
-
-
-
-
-
-
-
-
- Welcome Message (Max len. 1000)
-
-
-
-
-
-
-
-
-
-
-
- Channel Category
-
-
-
-
-
-
- Ticket Naming Scheme
-
-
-
- Ticket ID (#ticket-1)
-
-
-
-
-
- Username (#ryan-1)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
diff --git a/public/templates/views/tags.tmpl b/public/templates/views/tags.tmpl
deleted file mode 100644
index 9af48f7..0000000
--- a/public/templates/views/tags.tmpl
+++ /dev/null
@@ -1,124 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
-
- ID
- Content
- Delete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/teams.tmpl b/public/templates/views/teams.tmpl
deleted file mode 100644
index 4ac8d28..0000000
--- a/public/templates/views/teams.tmpl
+++ /dev/null
@@ -1,348 +0,0 @@
-{{define "content"}}
-
-
-
-
- Support Teams
-
-
-
Create Team
-
-
-
- Create
-
-
-
-
Manage Teams
-
-
-
- Select a team
-
-
-
-
-
-
-
-
-
-
-
Add Member
-
-
-
-
-
-
-
- Add Selected User
-
-
-
-
-
Add Role
-
-
-
-
-
- Add Selected Role
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/ticketlist.tmpl b/public/templates/views/ticketlist.tmpl
deleted file mode 100644
index 7f4b67f..0000000
--- a/public/templates/views/ticketlist.tmpl
+++ /dev/null
@@ -1,58 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
- Ticket ID
- User
- Additional Members
- View
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/public/templates/views/ticketview.tmpl b/public/templates/views/ticketview.tmpl
deleted file mode 100644
index 3e776d8..0000000
--- a/public/templates/views/ticketview.tmpl
+++ /dev/null
@@ -1,148 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
Close Ticket
-
-
-
-
- Close Ticket
-
-
-
-
-
View Ticket
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
diff --git a/public/templates/views/whitelabel.tmpl b/public/templates/views/whitelabel.tmpl
deleted file mode 100644
index 3d384f5..0000000
--- a/public/templates/views/whitelabel.tmpl
+++ /dev/null
@@ -1,303 +0,0 @@
-{{define "content"}}
-
-
-
-
-
-
-
-
-
-
-
-
-
Note: You will not be able to view the token after submitting it
-
-
-
-
-
-
-
-
- Generate Invite Link
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Interactions Endpoint URL
-
-
-
-
-
-
-
-
- Create Slash Commands
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Error
- Date
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{end}}
\ No newline at end of file
diff --git a/utils/archiverclient.go b/utils/archiverclient.go
new file mode 100644
index 0000000..b9f5e32
--- /dev/null
+++ b/utils/archiverclient.go
@@ -0,0 +1,5 @@
+package utils
+
+import "github.com/TicketsBot/archiverclient"
+
+var ArchiverClient archiverclient.ArchiverClient
diff --git a/utils/discord/auth.go b/utils/discord/auth.go
index 5edf0b8..989c6f5 100644
--- a/utils/discord/auth.go
+++ b/utils/discord/auth.go
@@ -39,7 +39,7 @@ type (
}
)
-const TokenEndpoint = "https://discordapp.com/api/oauth2/token"
+const TokenEndpoint = "https://discord.com/api/oauth2/token"
func AccessToken(code string) (TokenResponse, error) {
data := TokenData{
diff --git a/utils/guildutils.go b/utils/guildutils.go
index 6bfbf74..793e027 100644
--- a/utils/guildutils.go
+++ b/utils/guildutils.go
@@ -21,7 +21,7 @@ func LoadGuilds(accessToken string, userId uint64) error {
var wrappedGuilds []database.UserGuild
- // endpoint's partial guild doesn't include ownerid
+ // endpoint's partial guild doesn't includes ownerid
// we only user cached guilds on the index page, so it doesn't matter if we don't have have the real owner id
// if the user isn't the owner, as we pull from the cache on other endpoints
for _, guild := range guilds {
diff --git a/utils/sessionutils.go b/utils/sessionutils.go
index a3ebf44..3ef510f 100644
--- a/utils/sessionutils.go
+++ b/utils/sessionutils.go
@@ -1,17 +1,20 @@
package utils
import (
+ "fmt"
"github.com/gin-gonic/contrib/sessions"
)
func IsLoggedIn(store sessions.Session) bool {
- return store.Get("access_token") != nil &&
- store.Get("expiry") != nil &&
- store.Get("refresh_token") != nil &&
- store.Get("userid") != nil &&
- store.Get("name") != nil &&
- store.Get("avatar") != nil &&
- store.Get("csrf") != nil
+ requiredKeys := []string{"access_token", "expiry", "refresh_token", "userid", "name", "avatar", "csrf"}
+ for _, key := range requiredKeys {
+ if store.Get(key) == nil {
+ fmt.Println(key)
+ return false
+ }
+ }
+
+ return true
}
func GetUserId(store sessions.Session) uint64 {