use shared cache

This commit is contained in:
Dot-Rar 2020-04-02 20:53:11 +01:00
parent 770d8925bf
commit 26172fbd89
86 changed files with 9138 additions and 9136 deletions

View File

@ -1,19 +1,19 @@
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
steps:
- checkout
# specify any bash command here prefixed with `run: `
- run: go get -v ./cmd/panel/
- run: go build ./cmd/panel/main.go
- store_artifacts:
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
steps:
- checkout
# specify any bash command here prefixed with `run: `
- run: go get -v ./cmd/panel/
- run: go build ./cmd/panel/main.go
- store_artifacts:
path: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}/main

18
.gitattributes vendored
View File

@ -1,9 +1,9 @@
public/static/css/animate.min.css linguist-vendored
public/static/css/bootstrap.min.css linguist-vendored
public/static/css/light-bootstrap-dashboard.css linguist-vendored
public/static/js/bootstrap-notify.js linguist-vendored
public/static/js/bootstrap-select.js linguist-vendored
public/static/js/bootstrap.min.js linguist-vendored
public/static/js/chartist.min.js linguist-vendored
public/static/js/jquery.3.2.1.min.js linguist-vendored
public/static/js/light-bootstrap-dashboard.js linguist-vendored
public/static/css/animate.min.css linguist-vendored
public/static/css/bootstrap.min.css linguist-vendored
public/static/css/light-bootstrap-dashboard.css linguist-vendored
public/static/js/bootstrap-notify.js linguist-vendored
public/static/js/bootstrap-select.js linguist-vendored
public/static/js/bootstrap.min.js linguist-vendored
public/static/js/chartist.min.js linguist-vendored
public/static/js/jquery.3.2.1.min.js linguist-vendored
public/static/js/light-bootstrap-dashboard.js linguist-vendored

6
.gitignore vendored
View File

@ -1,3 +1,3 @@
config.toml
*.iml
.idea
config.toml
*.iml
.idea

View File

@ -1 +1 @@
# GoPanel
# GoPanel

View File

@ -114,17 +114,20 @@ func UpdateSettingsHandler(ctx *gin.Context) {
// Archive channel
// Create a list of IDs
var channelIds []string
for _, c := range guild.Channels {
channelIds = append(channelIds, c.Id)
channelsChan := make(chan []table.Channel)
go table.GetCachedChannelsByGuild(guildId, channelsChan)
channels := <-channelsChan
var channelIds []int64
for _, channel := range channels {
channelIds = append(channelIds, channel.ChannelId)
}
// Update or archive channel
archiveChannelStr := ctx.PostForm("archivechannel")
if utils.Contains(channelIds, archiveChannelStr) {
// Error is impossible, as we check it's a valid channel already
parsed, _ := strconv.ParseInt(archiveChannelStr, 10, 64)
table.UpdateArchiveChannel(guildId, parsed)
archiveChannelId, err := strconv.ParseInt(archiveChannelStr, 10, 64)
if err == nil && utils.Contains(channelIds, archiveChannelId) {
table.UpdateArchiveChannel(guildId, archiveChannelId)
}
// Users can close

View File

@ -1,25 +1,25 @@
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)
ctx.Redirect(302, fmt.Sprintf("https://discordapp.com/oauth2/authorize?response_type=code&redirect_uri=%s&scope=identify+guilds&client_id=%d", redirect, config.Conf.Oauth.Id))
}
}
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)
ctx.Redirect(302, fmt.Sprintf("https://discordapp.com/oauth2/authorize?response_type=code&redirect_uri=%s&scope=identify+guilds&client_id=%d", redirect, config.Conf.Oauth.Id))
}
}

View File

@ -1,17 +1,17 @@
package root
import (
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
func LogoutHandler(ctx *gin.Context) {
store := sessions.Default(ctx)
if store == nil {
return
}
defer store.Save()
store.Clear()
ctx.Redirect(302, "https://ticketsbot.net")
}
package root
import (
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
func LogoutHandler(ctx *gin.Context) {
store := sessions.Default(ctx)
if store == nil {
return
}
defer store.Save()
store.Clear()
ctx.Redirect(302, "https://ticketsbot.net")
}

50
cache/redis.go vendored
View File

@ -1,25 +1,25 @@
package cache
import (
"fmt"
"github.com/TicketsBot/GoPanel/config"
"github.com/go-redis/redis"
)
type RedisClient struct {
*redis.Client
}
var Client RedisClient
func NewRedisClient() RedisClient {
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Conf.Redis.Host, config.Conf.Redis.Port),
Password: config.Conf.Redis.Password,
PoolSize: config.Conf.Redis.Threads,
})
return RedisClient{
client,
}
}
package cache
import (
"fmt"
"github.com/TicketsBot/GoPanel/config"
"github.com/go-redis/redis"
)
type RedisClient struct {
*redis.Client
}
var Client RedisClient
func NewRedisClient() RedisClient {
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Conf.Redis.Host, config.Conf.Redis.Port),
Password: config.Conf.Redis.Password,
PoolSize: config.Conf.Redis.Threads,
})
return RedisClient{
client,
}
}

42
cache/uriparser.go vendored
View File

@ -1,21 +1,21 @@
package cache
import "net/url"
type RedisURI struct {
Addr string
Password string
}
func ParseURI(raw string) RedisURI {
parsed, err := url.Parse(raw); if err != nil {
panic(err)
}
passwd, _ := parsed.User.Password()
return RedisURI{
Addr: parsed.Host,
Password: passwd,
}
}
package cache
import "net/url"
type RedisURI struct {
Addr string
Password string
}
func ParseURI(raw string) RedisURI {
parsed, err := url.Parse(raw); if err != nil {
panic(err)
}
passwd, _ := parsed.User.Password()
return RedisURI{
Addr: parsed.Host,
Password: passwd,
}
}

View File

@ -1,35 +1,35 @@
admins=["217617036749176833"]
[server]
host="0.0.0.0:3000"
baseUrl="http://localhost:3000"
mainSite="https://ticketsbot.net"
[server.ratelimit]
window=10
max=600
[server.session]
threads=10
secret="secret"
[oauth]
id=
secret=""
redirectUri=""
[mariadb]
host="127.0.0.1"
username="ryan"
password="ryan"
database="tickets"
threads=5
[bot]
token=""
premium-lookup-proxy-url="http://localhost:3000"
premium-lookup-proxy-key=""
[redis]
host="127.0.0.1"
port=6379
password=""
threads=5
admins=["217617036749176833"]
[server]
host="0.0.0.0:3000"
baseUrl="http://localhost:3000"
mainSite="https://ticketsbot.net"
[server.ratelimit]
window=10
max=600
[server.session]
threads=10
secret="secret"
[oauth]
id=
secret=""
redirectUri=""
[mariadb]
host="127.0.0.1"
username="ryan"
password="ryan"
database="tickets"
threads=5
[bot]
token=""
premium-lookup-proxy-url="http://localhost:3000"
premium-lookup-proxy-key=""
[redis]
host="127.0.0.1"
port=6379
password=""
threads=5

View File

@ -1,78 +1,78 @@
package config
import (
"github.com/BurntSushi/toml"
"io/ioutil"
)
type (
Config struct {
Admins []string
Server Server
Oauth Oauth
MariaDB MariaDB
Bot Bot
Redis Redis
}
Server struct {
Host string
BaseUrl string
MainSite string
Ratelimit Ratelimit
Session Session
}
Ratelimit struct {
Window int
Max int
}
Session struct {
Threads int
Secret string
}
Oauth struct {
Id int64
Secret string
RedirectUri string
}
MariaDB struct {
Host string
Username string
Password string
Database string
Threads int
}
Bot struct {
Token string
PremiumLookupProxyUrl string `toml:"premium-lookup-proxy-url"`
PremiumLookupProxyKey string `toml:"premium-lookup-proxy-key"`
}
Redis struct {
Host string
Port int
Password string
Threads int
}
)
var (
Conf Config
)
func LoadConfig() {
raw, err := ioutil.ReadFile("config.toml")
if err != nil {
panic(err)
}
_, err = toml.Decode(string(raw), &Conf)
if err != nil {
panic(err)
}
}
package config
import (
"github.com/BurntSushi/toml"
"io/ioutil"
)
type (
Config struct {
Admins []string
Server Server
Oauth Oauth
MariaDB MariaDB
Bot Bot
Redis Redis
}
Server struct {
Host string
BaseUrl string
MainSite string
Ratelimit Ratelimit
Session Session
}
Ratelimit struct {
Window int
Max int
}
Session struct {
Threads int
Secret string
}
Oauth struct {
Id int64
Secret string
RedirectUri string
}
MariaDB struct {
Host string
Username string
Password string
Database string
Threads int
}
Bot struct {
Token string
PremiumLookupProxyUrl string `toml:"premium-lookup-proxy-url"`
PremiumLookupProxyKey string `toml:"premium-lookup-proxy-key"`
}
Redis struct {
Host string
Port int
Password string
Threads int
}
)
var (
Conf Config
)
func LoadConfig() {
raw, err := ioutil.ReadFile("config.toml")
if err != nil {
panic(err)
}
_, err = toml.Decode(string(raw), &Conf)
if err != nil {
panic(err)
}
}

View File

@ -1,35 +1,35 @@
package database
import (
"fmt"
"github.com/TicketsBot/GoPanel/config"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
var (
Database gorm.DB
)
func ConnectToDatabase() {
uri := fmt.Sprintf(
"%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.Conf.MariaDB.Username,
config.Conf.MariaDB.Password,
config.Conf.MariaDB.Host,
config.Conf.MariaDB.Database,
)
db, err := gorm.Open("mysql", uri)
if err != nil {
panic(err)
}
db.DB().SetMaxOpenConns(config.Conf.MariaDB.Threads)
db.DB().SetMaxIdleConns(0)
db.Set("gorm:table_options", "charset=utf8mb4")
db.BlockGlobalUpdate(true)
Database = *db
}
package database
import (
"fmt"
"github.com/TicketsBot/GoPanel/config"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
var (
Database gorm.DB
)
func ConnectToDatabase() {
uri := fmt.Sprintf(
"%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.Conf.MariaDB.Username,
config.Conf.MariaDB.Password,
config.Conf.MariaDB.Host,
config.Conf.MariaDB.Database,
)
db, err := gorm.Open("mysql", uri)
if err != nil {
panic(err)
}
db.DB().SetMaxOpenConns(config.Conf.MariaDB.Threads)
db.DB().SetMaxIdleConns(0)
db.Set("gorm:table_options", "charset=utf8mb4")
db.BlockGlobalUpdate(true)
Database = *db
}

View File

@ -5,7 +5,7 @@ import (
)
type ArchiveChannel struct {
Guild int64 `gorm:"column:GUILDID"`
Guild int64 `gorm:"column:GUILDID"`
Channel int64 `gorm:"column:CHANNELID"`
}
@ -24,4 +24,3 @@ func GetArchiveChannel(guildId int64) int64 {
return channel.Channel
}

View File

@ -1,37 +1,37 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type BlacklistNode struct {
Assoc int `gorm:"column:ASSOCID;type:int;primary_key;auto_increment"`
Guild int64 `gorm:"column:GUILDID"`
User int64 `gorm:"column:USERID"`
}
func (BlacklistNode) TableName() string {
return "blacklist"
}
func IsBlacklisted(guildId, userId int64) bool {
var count int
database.Database.Table("blacklist").Where(&BlacklistNode{Guild: guildId, User: userId}).Count(&count)
return count > 0
}
func AddBlacklist(guildId, userId int64) {
database.Database.Create(&BlacklistNode{Guild: guildId, User: userId})
}
func RemoveBlacklist(guildId, userId int64) {
var node BlacklistNode
database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Take(&node)
database.Database.Delete(&node)
}
func GetBlacklistNodes(guildId int64) []BlacklistNode {
var nodes []BlacklistNode
database.Database.Where(&BlacklistNode{Guild: guildId}).Find(&nodes)
return nodes
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type BlacklistNode struct {
Assoc int `gorm:"column:ASSOCID;type:int;primary_key;auto_increment"`
Guild int64 `gorm:"column:GUILDID"`
User int64 `gorm:"column:USERID"`
}
func (BlacklistNode) TableName() string {
return "blacklist"
}
func IsBlacklisted(guildId, userId int64) bool {
var count int
database.Database.Table("blacklist").Where(&BlacklistNode{Guild: guildId, User: userId}).Count(&count)
return count > 0
}
func AddBlacklist(guildId, userId int64) {
database.Database.Create(&BlacklistNode{Guild: guildId, User: userId})
}
func RemoveBlacklist(guildId, userId int64) {
var node BlacklistNode
database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Take(&node)
database.Database.Delete(&node)
}
func GetBlacklistNodes(guildId int64) []BlacklistNode {
var nodes []BlacklistNode
database.Database.Where(&BlacklistNode{Guild: guildId}).Find(&nodes)
return nodes
}

View File

@ -1,25 +1,25 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type ChannelCategory struct {
GuildId int64 `gorm:"column:GUILDID"`
Category int64 `gorm:"column:CATEGORYID"`
}
func (ChannelCategory) TableName() string {
return "channelcategory"
}
func UpdateChannelCategory(guildId int64, categoryId int64) {
database.Database.Where(&ChannelCategory{GuildId: guildId}).Assign(&ChannelCategory{Category: categoryId}).FirstOrCreate(&ChannelCategory{})
}
func GetChannelCategory(guildId int64) int64 {
var category ChannelCategory
database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category)
return category.Category
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type ChannelCategory struct {
GuildId int64 `gorm:"column:GUILDID"`
Category int64 `gorm:"column:CATEGORYID"`
}
func (ChannelCategory) TableName() string {
return "channelcategory"
}
func UpdateChannelCategory(guildId int64, categoryId int64) {
database.Database.Where(&ChannelCategory{GuildId: guildId}).Assign(&ChannelCategory{Category: categoryId}).FirstOrCreate(&ChannelCategory{})
}
func GetChannelCategory(guildId int64) int64 {
var category ChannelCategory
database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category)
return category.Category
}

View File

@ -1,38 +1,38 @@
package table
import (
"encoding/base64"
"encoding/json"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils/discord/objects"
)
type GuildCache struct {
UserId string `gorm:"column:USERID;type:varchar(20)"` // Apparently I made this a VARCHAR in the JS version
Guilds string `gorm:"column:guilds;type:mediumtext"`
}
func (GuildCache) TableName() string {
return "guildscache"
}
func UpdateGuilds(userId string, guilds string) {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).Assign(&GuildCache{Guilds: guilds}).FirstOrCreate(&cache)
}
func GetGuilds(userId string) []objects.Guild {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).First(&cache)
decoded, err := base64.StdEncoding.DecodeString(cache.Guilds)
if err != nil {
return make([]objects.Guild, 0)
}
var guilds []objects.Guild
if err := json.Unmarshal(decoded, &guilds); err != nil {
return make([]objects.Guild, 0)
}
return guilds
}
package table
import (
"encoding/base64"
"encoding/json"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils/discord/objects"
)
type GuildCache struct {
UserId string `gorm:"column:USERID;type:varchar(20)"` // Apparently I made this a VARCHAR in the JS version
Guilds string `gorm:"column:guilds;type:mediumtext"`
}
func (GuildCache) TableName() string {
return "guildscache"
}
func UpdateGuilds(userId string, guilds string) {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).Assign(&GuildCache{Guilds: guilds}).FirstOrCreate(&cache)
}
func GetGuilds(userId string) []objects.Guild {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).First(&cache)
decoded, err := base64.StdEncoding.DecodeString(cache.Guilds)
if err != nil {
return make([]objects.Guild, 0)
}
var guilds []objects.Guild
if err := json.Unmarshal(decoded, &guilds); err != nil {
return make([]objects.Guild, 0)
}
return guilds
}

View File

@ -1,44 +1,44 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type PermissionNode struct {
GuildId int64 `gorm:"column:GUILDID"`
UserId int64 `gorm:"column:USERID"`
IsSupport bool `gorm:"column:ISSUPPORT"`
IsAdmin bool `gorm:"column:ISADMIN"`
}
func (PermissionNode) TableName() string {
return "permissions"
}
func GetAdminGuilds(userId int64) []int64 {
var nodes []PermissionNode
database.Database.Where(&PermissionNode{UserId: userId}).Find(&nodes)
ids := make([]int64, 0)
for _, node := range nodes {
ids = append(ids, node.GuildId)
}
return ids
}
func IsSupport(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsSupport
}
func IsAdmin(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin
}
func IsStaff(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin || node.IsSupport
}
package table
import "github.com/TicketsBot/GoPanel/database"
type PermissionNode struct {
GuildId int64 `gorm:"column:GUILDID"`
UserId int64 `gorm:"column:USERID"`
IsSupport bool `gorm:"column:ISSUPPORT"`
IsAdmin bool `gorm:"column:ISADMIN"`
}
func (PermissionNode) TableName() string {
return "permissions"
}
func GetAdminGuilds(userId int64) []int64 {
var nodes []PermissionNode
database.Database.Where(&PermissionNode{UserId: userId}).Find(&nodes)
ids := make([]int64, 0)
for _, node := range nodes {
ids = append(ids, node.GuildId)
}
return ids
}
func IsSupport(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsSupport
}
func IsAdmin(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin
}
func IsStaff(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin || node.IsSupport
}

View File

@ -1,37 +1,37 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type PingEveryone struct {
GuildId int64 `gorm:"column:GUILDID"`
PingEveryone bool `gorm:"column:PINGEVERYONE;type:TINYINT"`
}
func (PingEveryone) TableName() string {
return "pingeveryone"
}
// tldr I hate gorm
func UpdatePingEveryone(guildId int64, pingEveryone bool) {
var settings []PingEveryone
database.Database.Where(&PingEveryone{GuildId: guildId}).Find(&settings)
updated := PingEveryone{guildId, pingEveryone}
if len(settings) == 0 {
database.Database.Create(&updated)
} else {
database.Database.Table("pingeveryone").Where("GUILDID = ?", guildId).Update("PINGEVERYONE", pingEveryone)
}
//database.Database.Where(&PingEveryone{GuildId: guildId}).Assign(&updated).FirstOrCreate(&PingEveryone{})
}
func GetPingEveryone(guildId int64) bool {
pingEveryone := PingEveryone{PingEveryone: true}
database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone)
return pingEveryone.PingEveryone
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type PingEveryone struct {
GuildId int64 `gorm:"column:GUILDID"`
PingEveryone bool `gorm:"column:PINGEVERYONE;type:TINYINT"`
}
func (PingEveryone) TableName() string {
return "pingeveryone"
}
// tldr I hate gorm
func UpdatePingEveryone(guildId int64, pingEveryone bool) {
var settings []PingEveryone
database.Database.Where(&PingEveryone{GuildId: guildId}).Find(&settings)
updated := PingEveryone{guildId, pingEveryone}
if len(settings) == 0 {
database.Database.Create(&updated)
} else {
database.Database.Table("pingeveryone").Where("GUILDID = ?", guildId).Update("PINGEVERYONE", pingEveryone)
}
//database.Database.Where(&PingEveryone{GuildId: guildId}).Assign(&updated).FirstOrCreate(&PingEveryone{})
}
func GetPingEveryone(guildId int64) bool {
pingEveryone := PingEveryone{PingEveryone: true}
database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone)
return pingEveryone.PingEveryone
}

View File

@ -1,25 +1,25 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type Prefix struct {
GuildId int64 `gorm:"column:GUILDID"`
Prefix string `gorm:"column:PREFIX;type:varchar(8)"`
}
func (Prefix) TableName() string {
return "prefix"
}
func UpdatePrefix(guildId int64, prefix string) {
database.Database.Where(&Prefix{GuildId: guildId}).Assign(&Prefix{Prefix: prefix}).FirstOrCreate(&Prefix{})
}
func GetPrefix(guildId int64) string {
prefix := Prefix{Prefix: "t!"}
database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix)
return prefix.Prefix
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type Prefix struct {
GuildId int64 `gorm:"column:GUILDID"`
Prefix string `gorm:"column:PREFIX;type:varchar(8)"`
}
func (Prefix) TableName() string {
return "prefix"
}
func UpdatePrefix(guildId int64, prefix string) {
database.Database.Where(&Prefix{GuildId: guildId}).Assign(&Prefix{Prefix: prefix}).FirstOrCreate(&Prefix{})
}
func GetPrefix(guildId int64) string {
prefix := Prefix{Prefix: "t!"}
database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix)
return prefix.Prefix
}

View File

@ -1,50 +1,50 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketArchive struct {
Uuid string `gorm:"column:UUID;type:varchar(36)"`
Guild int64 `gorm:"column:GUILDID"`
User int64 `gorm:"column:USERID"`
Username string `gorm:"column:USERNAME;type:varchar(32)"`
TicketId int `gorm:"column:TICKETID"`
CdnUrl string `gorm:"column:CDNURL;type:varchar(100)"`
}
func (TicketArchive) TableName() string {
return "ticketarchive"
}
func GetTicketArchives(guildId int64) []TicketArchive {
var archives []TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId}).Order("TICKETID desc").Find(&archives)
return archives
}
func GetFilteredTicketArchives(guildId int64, userId int64, username string, ticketId int) []TicketArchive {
var archives []TicketArchive
query := database.Database.Where(&TicketArchive{Guild: guildId})
if userId != 0 {
query = query.Where(&TicketArchive{User: userId})
}
if username != "" {
query = query.Where(&TicketArchive{Username: username})
}
if ticketId != 0 {
query = query.Where(&TicketArchive{TicketId: ticketId})
}
query.Order("TICKETID desc").Find(&archives)
return archives
}
func GetCdnUrl(guildId int64, uuid string) string {
var archive TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId, Uuid: uuid}).First(&archive)
return archive.CdnUrl
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketArchive struct {
Uuid string `gorm:"column:UUID;type:varchar(36)"`
Guild int64 `gorm:"column:GUILDID"`
User int64 `gorm:"column:USERID"`
Username string `gorm:"column:USERNAME;type:varchar(32)"`
TicketId int `gorm:"column:TICKETID"`
CdnUrl string `gorm:"column:CDNURL;type:varchar(100)"`
}
func (TicketArchive) TableName() string {
return "ticketarchive"
}
func GetTicketArchives(guildId int64) []TicketArchive {
var archives []TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId}).Order("TICKETID desc").Find(&archives)
return archives
}
func GetFilteredTicketArchives(guildId int64, userId int64, username string, ticketId int) []TicketArchive {
var archives []TicketArchive
query := database.Database.Where(&TicketArchive{Guild: guildId})
if userId != 0 {
query = query.Where(&TicketArchive{User: userId})
}
if username != "" {
query = query.Where(&TicketArchive{Username: username})
}
if ticketId != 0 {
query = query.Where(&TicketArchive{TicketId: ticketId})
}
query.Order("TICKETID desc").Find(&archives)
return archives
}
func GetCdnUrl(guildId int64, uuid string) string {
var archive TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId, Uuid: uuid}).First(&archive)
return archive.CdnUrl
}

View File

@ -1,25 +1,25 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketLimit struct {
GuildId int64 `gorm:"column:GUILDID"`
Limit int `gorm:"column:TICKETLIMIT"`
}
func (TicketLimit) TableName() string {
return "ticketlimit"
}
func UpdateTicketLimit(guildId int64, limit int) {
database.Database.Where(&TicketLimit{GuildId: guildId}).Assign(&TicketLimit{Limit: limit}).FirstOrCreate(&TicketLimit{})
}
func GetTicketLimit(guildId int64) int {
limit := TicketLimit{Limit: 5}
database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit)
return limit.Limit
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketLimit struct {
GuildId int64 `gorm:"column:GUILDID"`
Limit int `gorm:"column:TICKETLIMIT"`
}
func (TicketLimit) TableName() string {
return "ticketlimit"
}
func UpdateTicketLimit(guildId int64, limit int) {
database.Database.Where(&TicketLimit{GuildId: guildId}).Assign(&TicketLimit{Limit: limit}).FirstOrCreate(&TicketLimit{})
}
func GetTicketLimit(guildId int64) int {
limit := TicketLimit{Limit: 5}
database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit)
return limit.Limit
}

View File

@ -1,42 +1,42 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type Ticket struct {
Uuid string `gorm:"column:UUID;type:varchar(36);primary_key"`
TicketId int `gorm:"column:ID"`
Guild int64 `gorm:"column:GUILDID"`
Channel int64 `gorm:"column:CHANNELID"`
Owner int64 `gorm:"column:OWNERID"`
Members string `gorm:"column:MEMBERS;type:text"`
IsOpen bool `gorm:"column:OPEN"`
OpenTime int64 `gorm:"column:OPENTIME"`
}
func (Ticket) TableName() string {
return "tickets"
}
func GetTickets(guild int64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild}).Order("ID asc").Find(&tickets)
return tickets
}
func GetOpenTickets(guild int64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild, IsOpen: true}).Order("ID asc").Find(&tickets)
return tickets
}
func GetTicket(uuid string, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
ch <- ticket
}
func GetTicketById(guild int64, id int, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Guild: guild, TicketId: id}).First(&ticket)
ch <- ticket
}
package table
import "github.com/TicketsBot/GoPanel/database"
type Ticket struct {
Uuid string `gorm:"column:UUID;type:varchar(36);primary_key"`
TicketId int `gorm:"column:ID"`
Guild int64 `gorm:"column:GUILDID"`
Channel int64 `gorm:"column:CHANNELID"`
Owner int64 `gorm:"column:OWNERID"`
Members string `gorm:"column:MEMBERS;type:text"`
IsOpen bool `gorm:"column:OPEN"`
OpenTime int64 `gorm:"column:OPENTIME"`
}
func (Ticket) TableName() string {
return "tickets"
}
func GetTickets(guild int64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild}).Order("ID asc").Find(&tickets)
return tickets
}
func GetOpenTickets(guild int64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild, IsOpen: true}).Order("ID asc").Find(&tickets)
return tickets
}
func GetTicket(uuid string, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
ch <- ticket
}
func GetTicketById(guild int64, id int, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Guild: guild, TicketId: id}).First(&ticket)
ch <- ticket
}

View File

@ -1,34 +1,34 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type UsernameNode struct {
Id int64 `gorm:"column:USERID;primary_key"`
Name string `gorm:"column:USERNAME;type:text"` // Base 64 encoded
Discriminator string `gorm:"column:DISCRIM;type:varchar(4)"`
Avatar string `gorm:"column:AVATARHASH;type:varchar(100)"`
}
func (UsernameNode) TableName() string {
return "usernames"
}
func GetUsername(id int64, ch chan string) {
node := UsernameNode{Name: "Unknown"}
database.Database.Where(&UsernameNode{Id: id}).First(&node)
ch <- node.Name
}
func GetUserNodes(ids []int64) []UsernameNode {
var nodes []UsernameNode
database.Database.Where(ids).Find(&nodes)
return nodes
}
func GetUserId(name, discrim string) int64 {
var node UsernameNode
database.Database.Where(&UsernameNode{Name: name, Discriminator: discrim}).First(&node)
return node.Id
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type UsernameNode struct {
Id int64 `gorm:"column:USERID;primary_key"`
Name string `gorm:"column:USERNAME;type:text"` // Base 64 encoded
Discriminator string `gorm:"column:DISCRIM;type:varchar(4)"`
Avatar string `gorm:"column:AVATARHASH;type:varchar(100)"`
}
func (UsernameNode) TableName() string {
return "usernames"
}
func GetUsername(id int64, ch chan string) {
node := UsernameNode{Name: "Unknown"}
database.Database.Where(&UsernameNode{Id: id}).First(&node)
ch <- node.Name
}
func GetUserNodes(ids []int64) []UsernameNode {
var nodes []UsernameNode
database.Database.Where(ids).Find(&nodes)
return nodes
}
func GetUserId(name, discrim string) int64 {
var node UsernameNode
database.Database.Where(&UsernameNode{Name: name, Discriminator: discrim}).First(&node)
return node.Id
}

View File

@ -1,27 +1,27 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type UserCanClose struct {
Guild int64 `gorm:"column:GUILDID;unique;primary_key"`
CanClose *bool `gorm:"column:CANCLOSE"`
}
func (UserCanClose) TableName() string {
return "usercanclose"
}
func IsUserCanClose(guild int64, ch chan bool) {
var node UserCanClose
database.Database.Where(UserCanClose{Guild: guild}).First(&node)
if node.CanClose == nil {
ch <- true
} else {
ch <- *node.CanClose
}
}
func SetUserCanClose(guild int64, value bool) {
database.Database.Where(&UserCanClose{Guild: guild}).Assign(&UserCanClose{CanClose: &value}).FirstOrCreate(&UserCanClose{})
}
package table
import "github.com/TicketsBot/GoPanel/database"
type UserCanClose struct {
Guild int64 `gorm:"column:GUILDID;unique;primary_key"`
CanClose *bool `gorm:"column:CANCLOSE"`
}
func (UserCanClose) TableName() string {
return "usercanclose"
}
func IsUserCanClose(guild int64, ch chan bool) {
var node UserCanClose
database.Database.Where(UserCanClose{Guild: guild}).First(&node)
if node.CanClose == nil {
ch <- true
} else {
ch <- *node.CanClose
}
}
func SetUserCanClose(guild int64, value bool) {
database.Database.Where(&UserCanClose{Guild: guild}).Assign(&UserCanClose{CanClose: &value}).FirstOrCreate(&UserCanClose{})
}

View File

@ -1,22 +1,22 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
"time"
)
type Votes struct {
Id int64 `gorm:"type:bigint;unique_index;primary_key"`
VoteTime time.Time
}
func (Votes) TableName() string {
return "votes"
}
func HasVoted(owner int64, ch chan bool) {
var node Votes
database.Database.Where(Votes{Id: owner}).First(&node)
ch <- time.Now().Sub(node.VoteTime) < 24 * time.Hour
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
"time"
)
type Votes struct {
Id int64 `gorm:"type:bigint;unique_index;primary_key"`
VoteTime time.Time
}
func (Votes) TableName() string {
return "votes"
}
func HasVoted(owner int64, ch chan bool) {
var node Votes
database.Database.Where(Votes{Id: owner}).First(&node)
ch <- time.Now().Sub(node.VoteTime) < 24 * time.Hour
}

View File

@ -1,25 +1,25 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type WelcomeMessage struct {
GuildId int64 `gorm:"column:GUILDID"`
Message string `gorm:"column:MESSAGE;type:text"`
}
func (WelcomeMessage) TableName() string {
return "welcomemessages"
}
func UpdateWelcomeMessage(guildId int64, message string) {
database.Database.Where(&WelcomeMessage{GuildId: guildId}).Assign(&WelcomeMessage{Message: message}).FirstOrCreate(&WelcomeMessage{})
}
func GetWelcomeMessage(guildId int64) string {
message := WelcomeMessage{Message: "No message specified"}
database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message)
return message.Message
}
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type WelcomeMessage struct {
GuildId int64 `gorm:"column:GUILDID"`
Message string `gorm:"column:MESSAGE;type:text"`
}
func (WelcomeMessage) TableName() string {
return "welcomemessages"
}
func UpdateWelcomeMessage(guildId int64, message string) {
database.Database.Where(&WelcomeMessage{GuildId: guildId}).Assign(&WelcomeMessage{Message: message}).FirstOrCreate(&WelcomeMessage{})
}
func GetWelcomeMessage(guildId int64) string {
message := WelcomeMessage{Message: "No message specified"}
database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message)
return message.Message
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,69 +1,69 @@
@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%;
}
.form-control:focus {
border-color: #2e3136 !important;
background-color: #2e3136 !important;
color: white !important;
}
.message-input:focus {
border-color: #2e3136 !important;
background-color: #2e3136 !important;
color: white !important;
}
@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%;
}
.form-control:focus {
border-color: #2e3136 !important;
background-color: #2e3136 !important;
color: white !important;
}
.message-input:focus {
border-color: #2e3136 !important;
background-color: #2e3136 !important;
color: white !important;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,39 @@
body {
font-family: 'Open Sans',sans-serif !important;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
.sidebar {
background: url("/assets/img/sidebar-2.jpg");
background-size: cover;
overflow-x: hidden !important;
}
.sidebar-bottom {
position: absolute !important;
width: 100%;
}
.nav-link {
color: white !important;
}
.filterCard {
cursor: pointer;
}
.table td, .table th {
text-align: center;
}
.close-container {
text-align: right;
padding-bottom: 10px;
}
.input-fill {
margin: 0 !important;
padding: 0 !important;
}
body {
font-family: 'Open Sans',sans-serif !important;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
.sidebar {
background: url("/assets/img/sidebar-2.jpg");
background-size: cover;
overflow-x: hidden !important;
}
.sidebar-bottom {
position: absolute !important;
width: 100%;
}
.nav-link {
color: white !important;
}
.filterCard {
cursor: pointer;
}
.table td, .table th {
text-align: center;
}
.close-container {
text-align: right;
padding-bottom: 10px;
}
.input-fill {
margin: 0 !important;
padding: 0 !important;
}

View File

@ -1,14 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="white">
<circle transform="translate(8 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(16 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.3"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(24 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.6"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="white">
<circle transform="translate(8 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(16 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.3"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(24 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.6"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
</svg>

Before

Width:  |  Height:  |  Size: 950 B

After

Width:  |  Height:  |  Size: 964 B

View File

@ -1,404 +1,404 @@
/*
Creative Tim Modifications
Lines: 239, 240 was changed from top: 5px to top: 50% and we added margin-top: -13px. In this way the close button will be aligned vertically
Line:242 - modified when the icon is set, we add the class "alert-with-icon", so there will be enough space for the icon.
*/
/*
* Project: Bootstrap Notify = v3.1.5
* Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
* Author: Mouse0270 aka Robert McIntosh
* License: MIT License
* Website: https://github.com/mouse0270/bootstrap-growl
*/
/* global define:false, require: false, jQuery:false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
// Create the defaults once
var defaults = {
element: 'body',
position: null,
type: "info",
allow_dismiss: true,
allow_duplicates: true,
newest_on_top: false,
showProgressbar: false,
placement: {
from: "top",
align: "right"
},
offset: 20,
spacing: 10,
z_index: 1031,
delay: 5000,
timer: 1000,
url_target: '_blank',
mouse_over: null,
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
onShow: null,
onShown: null,
onClose: null,
onClosed: null,
icon_type: 'class',
template: '<div data-notify="container" class="col-xs-11 col-sm-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss">&times;</button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
};
String.format = function () {
var str = arguments[0];
for (var i = 1; i < arguments.length; i++) {
str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]);
}
return str;
};
function isDuplicateNotification(notification) {
var isDupe = false;
$('[data-notify="container"]').each(function (i, el) {
var $el = $(el);
var title = $el.find('[data-notify="title"]').text().trim();
var message = $el.find('[data-notify="message"]').html().trim();
// The input string might be different than the actual parsed HTML string!
// (<br> vs <br /> for example)
// So we have to force-parse this as HTML here!
var isSameTitle = title === $("<div>" + notification.settings.content.title + "</div>").html().trim();
var isSameMsg = message === $("<div>" + notification.settings.content.message + "</div>").html().trim();
var isSameType = $el.hasClass('alert-' + notification.settings.type);
if (isSameTitle && isSameMsg && isSameType) {
//we found the dupe. Set the var and stop checking.
isDupe = true;
}
return !isDupe;
});
return isDupe;
}
function Notify(element, content, options) {
// Setup Content of Notify
var contentObj = {
content: {
message: typeof content === 'object' ? content.message : content,
title: content.title ? content.title : '',
icon: content.icon ? content.icon : '',
url: content.url ? content.url : '#',
target: content.target ? content.target : '-'
}
};
options = $.extend(true, {}, contentObj, options);
this.settings = $.extend(true, {}, defaults, options);
this._defaults = defaults;
if (this.settings.content.target === "-") {
this.settings.content.target = this.settings.url_target;
}
this.animations = {
start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
};
if (typeof this.settings.offset === 'number') {
this.settings.offset = {
x: this.settings.offset,
y: this.settings.offset
};
}
//if duplicate messages are not allowed, then only continue if this new message is not a duplicate of one that it already showing
if (this.settings.allow_duplicates || (!this.settings.allow_duplicates && !isDuplicateNotification(this))) {
this.init();
}
}
$.extend(Notify.prototype, {
init: function () {
var self = this;
this.buildNotify();
if (this.settings.content.icon) {
this.setIcon();
}
if (this.settings.content.url != "#") {
this.styleURL();
}
this.styleDismiss();
this.placement();
this.bind();
this.notify = {
$ele: this.$ele,
update: function (command, update) {
var commands = {};
if (typeof command === "string") {
commands[command] = update;
} else {
commands = command;
}
for (var cmd in commands) {
switch (cmd) {
case "type":
this.$ele.removeClass('alert-' + self.settings.type);
this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
self.settings.type = commands[cmd];
this.$ele.addClass('alert-' + commands[cmd]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[cmd]);
break;
case "icon":
var $icon = this.$ele.find('[data-notify="icon"]');
if (self.settings.icon_type.toLowerCase() === 'class') {
$icon.removeClass(self.settings.content.icon).addClass(commands[cmd]);
} else {
if (!$icon.is('img')) {
$icon.find('img');
}
$icon.attr('src', commands[cmd]);
}
break;
case "progress":
var newDelay = self.settings.delay - (self.settings.delay * (commands[cmd] / 100));
this.$ele.data('notify-delay', newDelay);
this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[cmd]).css('width', commands[cmd] + '%');
break;
case "url":
this.$ele.find('[data-notify="url"]').attr('href', commands[cmd]);
break;
case "target":
this.$ele.find('[data-notify="url"]').attr('target', commands[cmd]);
break;
default:
this.$ele.find('[data-notify="' + cmd + '"]').html(commands[cmd]);
}
}
var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
self.reposition(posX);
},
close: function () {
self.close();
}
};
},
buildNotify: function () {
var content = this.settings.content;
this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
if (!this.settings.allow_dismiss) {
this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
}
if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
this.$ele.find('[data-notify="progressbar"]').remove();
}
},
setIcon: function () {
this.$ele.addClass('alert-with-icon');
if (this.settings.icon_type.toLowerCase() === 'class') {
this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon);
} else {
if (this.$ele.find('[data-notify="icon"]').is('img')) {
this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
} else {
this.$ele.find('[data-notify="icon"]').append('<img src="' + this.settings.content.icon + '" alt="Notify Icon" />');
}
}
},
styleDismiss: function () {
this.$ele.find('[data-notify="dismiss"]').css({
position: 'absolute',
right: '10px',
top: '50%',
marginTop: '-13px',
zIndex: this.settings.z_index + 2
});
},
styleURL: function () {
this.$ele.find('[data-notify="url"]').css({
backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%',
zIndex: this.settings.z_index + 1
});
},
placement: function () {
var self = this,
offsetAmt = this.settings.offset.y,
css = {
display: 'inline-block',
margin: '0px auto',
position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
transition: 'all .5s ease-in-out',
zIndex: this.settings.z_index
},
hasAnimation = false,
settings = this.settings;
$('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function () {
offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
});
if (this.settings.newest_on_top === true) {
offsetAmt = this.settings.offset.y;
}
css[this.settings.placement.from] = offsetAmt + 'px';
switch (this.settings.placement.align) {
case "left":
case "right":
css[this.settings.placement.align] = this.settings.offset.x + 'px';
break;
case "center":
css.left = 0;
css.right = 0;
break;
}
this.$ele.css(css).addClass(this.settings.animate.enter);
$.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function (index, prefix) {
self.$ele[0].style[prefix + 'AnimationIterationCount'] = 1;
});
$(this.settings.element).append(this.$ele);
if (this.settings.newest_on_top === true) {
offsetAmt = (parseInt(offsetAmt) + parseInt(this.settings.spacing)) + this.$ele.outerHeight();
this.reposition(offsetAmt);
}
if ($.isFunction(self.settings.onShow)) {
self.settings.onShow.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
}
}, 600);
},
bind: function () {
var self = this;
this.$ele.find('[data-notify="dismiss"]').on('click', function () {
self.close();
});
this.$ele.mouseover(function () {
$(this).data('data-hover', "true");
}).mouseout(function () {
$(this).data('data-hover', "false");
});
this.$ele.data('data-hover', "false");
if (this.settings.delay > 0) {
self.$ele.data('notify-delay', self.settings.delay);
var timer = setInterval(function () {
var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over === "pause") || self.settings.mouse_over != "pause") {
var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
self.$ele.data('notify-delay', delay);
self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
}
if (delay <= -(self.settings.timer)) {
clearInterval(timer);
self.close();
}
}, self.settings.timer);
}
},
close: function () {
var self = this,
posX = parseInt(this.$ele.css(this.settings.placement.from)),
hasAnimation = false;
this.$ele.data('closing', 'true').addClass(this.settings.animate.exit);
self.reposition(posX);
if ($.isFunction(self.settings.onClose)) {
self.settings.onClose.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
$(this).remove();
if ($.isFunction(self.settings.onClosed)) {
self.settings.onClosed.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
self.$ele.remove();
if (self.settings.onClosed) {
self.settings.onClosed(self.$ele);
}
}
}, 600);
},
reposition: function (posX) {
var self = this,
notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
$elements = this.$ele.nextAll(notifies);
if (this.settings.newest_on_top === true) {
$elements = this.$ele.prevAll(notifies);
}
$elements.each(function () {
$(this).css(self.settings.placement.from, posX);
posX = (parseInt(posX) + parseInt(self.settings.spacing)) + $(this).outerHeight();
});
}
});
$.notify = function (content, options) {
var plugin = new Notify(this, content, options);
return plugin.notify;
};
$.notifyDefaults = function (options) {
defaults = $.extend(true, {}, defaults, options);
return defaults;
};
$.notifyClose = function (command) {
if (typeof command === "undefined" || command === "all") {
$('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
} else {
$('[data-notify-position="' + command + '"]').find('[data-notify="dismiss"]').trigger('click');
}
};
}));
/*
Creative Tim Modifications
Lines: 239, 240 was changed from top: 5px to top: 50% and we added margin-top: -13px. In this way the close button will be aligned vertically
Line:242 - modified when the icon is set, we add the class "alert-with-icon", so there will be enough space for the icon.
*/
/*
* Project: Bootstrap Notify = v3.1.5
* Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
* Author: Mouse0270 aka Robert McIntosh
* License: MIT License
* Website: https://github.com/mouse0270/bootstrap-growl
*/
/* global define:false, require: false, jQuery:false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
// Create the defaults once
var defaults = {
element: 'body',
position: null,
type: "info",
allow_dismiss: true,
allow_duplicates: true,
newest_on_top: false,
showProgressbar: false,
placement: {
from: "top",
align: "right"
},
offset: 20,
spacing: 10,
z_index: 1031,
delay: 5000,
timer: 1000,
url_target: '_blank',
mouse_over: null,
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
onShow: null,
onShown: null,
onClose: null,
onClosed: null,
icon_type: 'class',
template: '<div data-notify="container" class="col-xs-11 col-sm-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss">&times;</button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
};
String.format = function () {
var str = arguments[0];
for (var i = 1; i < arguments.length; i++) {
str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]);
}
return str;
};
function isDuplicateNotification(notification) {
var isDupe = false;
$('[data-notify="container"]').each(function (i, el) {
var $el = $(el);
var title = $el.find('[data-notify="title"]').text().trim();
var message = $el.find('[data-notify="message"]').html().trim();
// The input string might be different than the actual parsed HTML string!
// (<br> vs <br /> for example)
// So we have to force-parse this as HTML here!
var isSameTitle = title === $("<div>" + notification.settings.content.title + "</div>").html().trim();
var isSameMsg = message === $("<div>" + notification.settings.content.message + "</div>").html().trim();
var isSameType = $el.hasClass('alert-' + notification.settings.type);
if (isSameTitle && isSameMsg && isSameType) {
//we found the dupe. Set the var and stop checking.
isDupe = true;
}
return !isDupe;
});
return isDupe;
}
function Notify(element, content, options) {
// Setup Content of Notify
var contentObj = {
content: {
message: typeof content === 'object' ? content.message : content,
title: content.title ? content.title : '',
icon: content.icon ? content.icon : '',
url: content.url ? content.url : '#',
target: content.target ? content.target : '-'
}
};
options = $.extend(true, {}, contentObj, options);
this.settings = $.extend(true, {}, defaults, options);
this._defaults = defaults;
if (this.settings.content.target === "-") {
this.settings.content.target = this.settings.url_target;
}
this.animations = {
start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
};
if (typeof this.settings.offset === 'number') {
this.settings.offset = {
x: this.settings.offset,
y: this.settings.offset
};
}
//if duplicate messages are not allowed, then only continue if this new message is not a duplicate of one that it already showing
if (this.settings.allow_duplicates || (!this.settings.allow_duplicates && !isDuplicateNotification(this))) {
this.init();
}
}
$.extend(Notify.prototype, {
init: function () {
var self = this;
this.buildNotify();
if (this.settings.content.icon) {
this.setIcon();
}
if (this.settings.content.url != "#") {
this.styleURL();
}
this.styleDismiss();
this.placement();
this.bind();
this.notify = {
$ele: this.$ele,
update: function (command, update) {
var commands = {};
if (typeof command === "string") {
commands[command] = update;
} else {
commands = command;
}
for (var cmd in commands) {
switch (cmd) {
case "type":
this.$ele.removeClass('alert-' + self.settings.type);
this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
self.settings.type = commands[cmd];
this.$ele.addClass('alert-' + commands[cmd]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[cmd]);
break;
case "icon":
var $icon = this.$ele.find('[data-notify="icon"]');
if (self.settings.icon_type.toLowerCase() === 'class') {
$icon.removeClass(self.settings.content.icon).addClass(commands[cmd]);
} else {
if (!$icon.is('img')) {
$icon.find('img');
}
$icon.attr('src', commands[cmd]);
}
break;
case "progress":
var newDelay = self.settings.delay - (self.settings.delay * (commands[cmd] / 100));
this.$ele.data('notify-delay', newDelay);
this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[cmd]).css('width', commands[cmd] + '%');
break;
case "url":
this.$ele.find('[data-notify="url"]').attr('href', commands[cmd]);
break;
case "target":
this.$ele.find('[data-notify="url"]').attr('target', commands[cmd]);
break;
default:
this.$ele.find('[data-notify="' + cmd + '"]').html(commands[cmd]);
}
}
var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
self.reposition(posX);
},
close: function () {
self.close();
}
};
},
buildNotify: function () {
var content = this.settings.content;
this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
if (!this.settings.allow_dismiss) {
this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
}
if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
this.$ele.find('[data-notify="progressbar"]').remove();
}
},
setIcon: function () {
this.$ele.addClass('alert-with-icon');
if (this.settings.icon_type.toLowerCase() === 'class') {
this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon);
} else {
if (this.$ele.find('[data-notify="icon"]').is('img')) {
this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
} else {
this.$ele.find('[data-notify="icon"]').append('<img src="' + this.settings.content.icon + '" alt="Notify Icon" />');
}
}
},
styleDismiss: function () {
this.$ele.find('[data-notify="dismiss"]').css({
position: 'absolute',
right: '10px',
top: '50%',
marginTop: '-13px',
zIndex: this.settings.z_index + 2
});
},
styleURL: function () {
this.$ele.find('[data-notify="url"]').css({
backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%',
zIndex: this.settings.z_index + 1
});
},
placement: function () {
var self = this,
offsetAmt = this.settings.offset.y,
css = {
display: 'inline-block',
margin: '0px auto',
position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
transition: 'all .5s ease-in-out',
zIndex: this.settings.z_index
},
hasAnimation = false,
settings = this.settings;
$('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function () {
offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
});
if (this.settings.newest_on_top === true) {
offsetAmt = this.settings.offset.y;
}
css[this.settings.placement.from] = offsetAmt + 'px';
switch (this.settings.placement.align) {
case "left":
case "right":
css[this.settings.placement.align] = this.settings.offset.x + 'px';
break;
case "center":
css.left = 0;
css.right = 0;
break;
}
this.$ele.css(css).addClass(this.settings.animate.enter);
$.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function (index, prefix) {
self.$ele[0].style[prefix + 'AnimationIterationCount'] = 1;
});
$(this.settings.element).append(this.$ele);
if (this.settings.newest_on_top === true) {
offsetAmt = (parseInt(offsetAmt) + parseInt(this.settings.spacing)) + this.$ele.outerHeight();
this.reposition(offsetAmt);
}
if ($.isFunction(self.settings.onShow)) {
self.settings.onShow.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
}
}, 600);
},
bind: function () {
var self = this;
this.$ele.find('[data-notify="dismiss"]').on('click', function () {
self.close();
});
this.$ele.mouseover(function () {
$(this).data('data-hover', "true");
}).mouseout(function () {
$(this).data('data-hover', "false");
});
this.$ele.data('data-hover', "false");
if (this.settings.delay > 0) {
self.$ele.data('notify-delay', self.settings.delay);
var timer = setInterval(function () {
var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over === "pause") || self.settings.mouse_over != "pause") {
var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
self.$ele.data('notify-delay', delay);
self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
}
if (delay <= -(self.settings.timer)) {
clearInterval(timer);
self.close();
}
}, self.settings.timer);
}
},
close: function () {
var self = this,
posX = parseInt(this.$ele.css(this.settings.placement.from)),
hasAnimation = false;
this.$ele.data('closing', 'true').addClass(this.settings.animate.exit);
self.reposition(posX);
if ($.isFunction(self.settings.onClose)) {
self.settings.onClose.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
$(this).remove();
if ($.isFunction(self.settings.onClosed)) {
self.settings.onClosed.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
self.$ele.remove();
if (self.settings.onClosed) {
self.settings.onClosed(self.$ele);
}
}
}, 600);
},
reposition: function (posX) {
var self = this,
notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
$elements = this.$ele.nextAll(notifies);
if (this.settings.newest_on_top === true) {
$elements = this.$ele.prevAll(notifies);
}
$elements.each(function () {
$(this).css(self.settings.placement.from, posX);
posX = (parseInt(posX) + parseInt(self.settings.spacing)) + $(this).outerHeight();
});
}
});
$.notify = function (content, options) {
var plugin = new Notify(this, content, options);
return plugin.notify;
};
$.notifyDefaults = function (options) {
defaults = $.extend(true, {}, defaults, options);
return defaults;
};
$.notifyClose = function (command) {
if (typeof command === "undefined" || command === "all") {
$('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
} else {
$('[data-notify-position="' + command + '"]').find('[data-notify="dismiss"]').trigger('click');
}
};
}));

View File

@ -1,438 +1,438 @@
!function($) {
var Selectpicker = function(element, options, e) {
if (e ) {
e.stopPropagation();
e.preventDefault();
}
this.$element = $(element);
this.$newElement = null;
this.button = null;
//Merge defaults, options and data-attributes to make our options
this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options);
//If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute
if(this.options.title==null)
this.options.title = this.$element.attr('title');
//Expose public methods
this.val = Selectpicker.prototype.val;
this.render = Selectpicker.prototype.render;
this.init();
};
Selectpicker.prototype = {
constructor: Selectpicker,
init: function (e) {
var _this = this;
this.$element.hide();
this.multiple = this.$element.prop('multiple');
var classList = this.$element.attr('class') !== undefined ? this.$element.attr('class').split(/\s+/) : '';
var id = this.$element.attr('id');
this.$element.after( this.createView() );
this.$newElement = this.$element.next('.select');
var select = this.$newElement;
var menu = this.$newElement.find('.dropdown-menu');
var menuArrow = this.$newElement.find('.dropdown-arrow');
var menuA = menu.find('li > a');
var liHeight = select.addClass('open').find('.dropdown-menu li > a').outerHeight();
select.removeClass('open');
var divHeight = menu.find('li .divider').outerHeight(true);
var selectOffset_top = this.$newElement.offset().top;
var size = 0;
var menuHeight = 0;
var selectHeight = this.$newElement.outerHeight();
this.button = this.$newElement.find('> button');
if (id !== undefined) {
this.button.attr('id', id);
$('label[for="' + id + '"]').click(function(){ select.find('button#'+id).focus(); })
}
for (var i = 0; i < classList.length; i++) {
if(classList[i] != 'selectpicker') {
this.$newElement.addClass(classList[i]);
}
}
//If we are multiple, then add the show-tick class by default
if(this.multiple) {
this.$newElement.addClass('select-multiple');
}
this.button.addClass(this.options.style);
menu.addClass(this.options.menuStyle);
menuArrow.addClass(function() {
if (_this.options.menuStyle) {
return _this.options.menuStyle.replace('dropdown-', 'dropdown-arrow-');
}
});
this.checkDisabled();
this.checkTabIndex();
this.clickListener();
var menuPadding = parseInt(menu.css('padding-top')) + parseInt(menu.css('padding-bottom')) + parseInt(menu.css('border-top-width')) + parseInt(menu.css('border-bottom-width'));
if (this.options.size == 'auto') {
// Creative Tim Changes: We changed the regular function made in bootstrap-select with this function so the getSize() will not be triggered one million times per second while you scroll.
var getSize = debounce(function() {
var selectOffset_top_scroll = selectOffset_top - $(window).scrollTop();
var windowHeight = $(window).innerHeight();
var menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2;
var selectOffset_bot = windowHeight - selectOffset_top_scroll - selectHeight - menuExtras;
menuHeight = selectOffset_bot;
if (select.hasClass('dropup')) {
menuHeight = selectOffset_top_scroll - menuExtras;
}
//limit menuHeight to 300px to have a smooth transition with cubic bezier on dropdown
if(menuHeight >= 300){
menuHeight = 300;
}
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'auto', 'min-height' : liHeight * 3 + 'px'});
}, 50);
getSize;
$(window).on('scroll', getSize);
$(window).on('resize', getSize);
if (window.MutationObserver) {
new MutationObserver(getSize).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', getSize);
}
} else if (this.options.size && this.options.size != 'auto' && menu.find('li').length > this.options.size) {
var optIndex = menu.find("li > *").filter(':not(.divider)').slice(0,this.options.size).last().parent().index();
var divLength = menu.find("li").slice(0,optIndex + 1).find('.divider').length;
menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding;
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'scroll'});
//console.log('sunt in if');
}
// Listen for updates to the DOM and re render... (Use Mutation Observer when availiable)
if (window.MutationObserver) {
new MutationObserver($.proxy(this.reloadLi, this)).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', $.proxy(this.reloadLi, this));
}
this.render();
},
createDropdown: function() {
var drop =
"<div class='btn-group select'>" +
"<button class='btn dropdown-toggle clearfix' data-toggle='dropdown'>" +
"<span class='filter-option'></span>&nbsp;" +
"<span class='caret'></span>" +
"</button>" +
"<span class='dropdown-arrow'></span>" +
"<ul class='dropdown-menu' role='menu'>" +
"</ul>" +
"</div>";
return $(drop);
},
createView: function() {
var $drop = this.createDropdown();
var $li = this.createLi();
$drop.find('ul').append($li);
return $drop;
},
reloadLi: function() {
//Remove all children.
this.destroyLi();
//Re build
$li = this.createLi();
this.$newElement.find('ul').append( $li );
//render view
this.render();
},
destroyLi:function() {
this.$newElement.find('li').remove();
},
createLi: function() {
var _this = this;
var _li = [];
var _liA = [];
var _liHtml = '';
this.$element.find('option').each(function(){
_li.push($(this).text());
});
this.$element.find('option').each(function(index) {
//Get the class and text for the option
var optionClass = $(this).attr("class") !== undefined ? $(this).attr("class") : '';
var text = $(this).text();
var subtext = $(this).data('subtext') !== undefined ? '<small class="muted">'+$(this).data('subtext')+'</small>' : '';
//Append any subtext to the main text.
text+=subtext;
if ($(this).parent().is('optgroup') && $(this).data('divider') != true) {
if ($(this).index() == 0) {
//Get the opt group label
var label = $(this).parent().attr('label');
var labelSubtext = $(this).parent().data('subtext') !== undefined ? '<small class="muted">'+$(this).parent().data('subtext')+'</small>' : '';
label += labelSubtext;
if ($(this)[0].index != 0) {
_liA.push(
'<div class="divider"></div>'+
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass )
);
} else {
_liA.push(
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass ));
}
} else {
_liA.push( _this.createA(text, "opt " + optionClass ) );
}
} else if ($(this).data('divider') == true) {
_liA.push('<div class="divider"></div>');
} else if ($(this).data('hidden') == true) {
_liA.push('');
} else {
_liA.push( _this.createA(text, optionClass ) );
}
});
if (_li.length > 0) {
for (var i = 0; i < _li.length; i++) {
var $option = this.$element.find('option').eq(i);
_liHtml += "<li rel=" + i + ">" + _liA[i] + "</li>";
}
}
//If we dont have a selected item, and we dont have a title, select the first element so something is set in the button
if(this.$element.find('option:selected').length==0 && !_this.options.title) {
this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
}
return $(_liHtml);
},
createA:function(test, classes) {
return '<a tabindex="-1" href="#" class="'+classes+'">' +
'<span class="">' + test + '</span>' +
'</a>';
},
render:function() {
var _this = this;
//Set width of select
if (this.options.width == 'auto') {
var ulWidth = this.$newElement.find('.dropdown-menu').css('width');
this.$newElement.css('width',ulWidth);
} else if (this.options.width && this.options.width != 'auto') {
this.$newElement.css('width',this.options.width);
}
//Update the LI to match the SELECT
this.$element.find('option').each(function(index) {
_this.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') );
_this.setSelected(index, $(this).is(':selected') );
});
var selectedItems = this.$element.find('option:selected').map(function(index,value) {
if($(this).attr('title')!=undefined) {
return $(this).attr('title');
} else {
return $(this).text();
}
}).toArray();
//Convert all the values into a comma delimited string
var title = selectedItems.join(", ");
//If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
if(_this.multiple && _this.options.selectedTextFormat.indexOf('count') > -1) {
var max = _this.options.selectedTextFormat.split(">");
if( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) {
title = selectedItems.length +' of ' + this.$element.find('option').length + ' selected';
}
}
//If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
if(!title) {
title = _this.options.title != undefined ? _this.options.title : _this.options.noneSelectedText;
}
this.$element.next('.select').find('.filter-option').html( title );
},
setSelected:function(index, selected) {
if(selected) {
this.$newElement.find('li').eq(index).addClass('selected');
} else {
this.$newElement.find('li').eq(index).removeClass('selected');
}
},
setDisabled:function(index, disabled) {
if(disabled) {
this.$newElement.find('li').eq(index).addClass('disabled');
} else {
this.$newElement.find('li').eq(index).removeClass('disabled');
}
},
checkDisabled: function() {
if (this.$element.is(':disabled')) {
this.button.addClass('disabled');
this.button.click(function(e) {
e.preventDefault();
});
}
},
checkTabIndex: function() {
if (this.$element.is('[tabindex]')) {
var tabindex = this.$element.attr("tabindex");
this.button.attr('tabindex', tabindex);
}
},
clickListener: function() {
var _this = this;
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
this.$newElement.on('click', 'li a', function(e){
var clickedIndex = $(this).parent().index(),
$this = $(this).parent(),
$select = $this.parents('.select');
//Dont close on multi choice menu
if(_this.multiple) {
e.stopPropagation();
}
e.preventDefault();
//Dont run if we have been disabled
if ($select.prev('select').not(':disabled') && !$(this).parent().hasClass('disabled')){
//Deselect all others if not multi select box
if (!_this.multiple) {
$select.prev('select').find('option').removeAttr('selected');
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
//Else toggle the one we have chosen if we are multi selet.
else {
var selected = $select.prev('select').find('option').eq(clickedIndex).prop('selected');
if(selected) {
$select.prev('select').find('option').eq(clickedIndex).removeAttr('selected');
} else {
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
}
$select.find('.filter-option').html($this.text());
$select.find('button').focus();
// Trigger select 'change'
$select.prev('select').trigger('change');
}
});
this.$newElement.on('click', 'li.disabled a, li dt, li .divider', function(e) {
e.preventDefault();
e.stopPropagation();
$select = $(this).parent().parents('.select');
$select.find('button').focus();
});
this.$element.on('change', function(e) {
_this.render();
});
},
val:function(value) {
if(value!=undefined) {
this.$element.val( value );
this.$element.trigger('change');
return this.$element;
} else {
return this.$element.val();
}
}
};
$.fn.selectpicker = function(option, event) {
//get the args of the outer function..
var args = arguments;
var value;
var chain = this.each(function () {
var $this = $(this),
data = $this.data('selectpicker'),
options = typeof option == 'object' && option;
if (!data) {
$this.data('selectpicker', (data = new Selectpicker(this, options, event)));
} else {
for(var i in option) {
data[i]=option[i];
}
}
if (typeof option == 'string') {
//Copy the value of option, as once we shift the arguments
//it also shifts the value of option.
property = option;
if(data[property] instanceof Function) {
[].shift.apply(args);
value = data[property].apply(data, args);
} else {
value = data[property];
}
}
});
if(value!=undefined) {
return value;
} else {
return chain;
}
};
$.fn.selectpicker.defaults = {
style: null,
size: 'auto',
title: null,
selectedTextFormat : 'values',
noneSelectedText : 'Nothing selected',
width: null,
menuStyle: null,
toggleSize: null
}
}(window.jQuery);
!function($) {
var Selectpicker = function(element, options, e) {
if (e ) {
e.stopPropagation();
e.preventDefault();
}
this.$element = $(element);
this.$newElement = null;
this.button = null;
//Merge defaults, options and data-attributes to make our options
this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options);
//If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute
if(this.options.title==null)
this.options.title = this.$element.attr('title');
//Expose public methods
this.val = Selectpicker.prototype.val;
this.render = Selectpicker.prototype.render;
this.init();
};
Selectpicker.prototype = {
constructor: Selectpicker,
init: function (e) {
var _this = this;
this.$element.hide();
this.multiple = this.$element.prop('multiple');
var classList = this.$element.attr('class') !== undefined ? this.$element.attr('class').split(/\s+/) : '';
var id = this.$element.attr('id');
this.$element.after( this.createView() );
this.$newElement = this.$element.next('.select');
var select = this.$newElement;
var menu = this.$newElement.find('.dropdown-menu');
var menuArrow = this.$newElement.find('.dropdown-arrow');
var menuA = menu.find('li > a');
var liHeight = select.addClass('open').find('.dropdown-menu li > a').outerHeight();
select.removeClass('open');
var divHeight = menu.find('li .divider').outerHeight(true);
var selectOffset_top = this.$newElement.offset().top;
var size = 0;
var menuHeight = 0;
var selectHeight = this.$newElement.outerHeight();
this.button = this.$newElement.find('> button');
if (id !== undefined) {
this.button.attr('id', id);
$('label[for="' + id + '"]').click(function(){ select.find('button#'+id).focus(); })
}
for (var i = 0; i < classList.length; i++) {
if(classList[i] != 'selectpicker') {
this.$newElement.addClass(classList[i]);
}
}
//If we are multiple, then add the show-tick class by default
if(this.multiple) {
this.$newElement.addClass('select-multiple');
}
this.button.addClass(this.options.style);
menu.addClass(this.options.menuStyle);
menuArrow.addClass(function() {
if (_this.options.menuStyle) {
return _this.options.menuStyle.replace('dropdown-', 'dropdown-arrow-');
}
});
this.checkDisabled();
this.checkTabIndex();
this.clickListener();
var menuPadding = parseInt(menu.css('padding-top')) + parseInt(menu.css('padding-bottom')) + parseInt(menu.css('border-top-width')) + parseInt(menu.css('border-bottom-width'));
if (this.options.size == 'auto') {
// Creative Tim Changes: We changed the regular function made in bootstrap-select with this function so the getSize() will not be triggered one million times per second while you scroll.
var getSize = debounce(function() {
var selectOffset_top_scroll = selectOffset_top - $(window).scrollTop();
var windowHeight = $(window).innerHeight();
var menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2;
var selectOffset_bot = windowHeight - selectOffset_top_scroll - selectHeight - menuExtras;
menuHeight = selectOffset_bot;
if (select.hasClass('dropup')) {
menuHeight = selectOffset_top_scroll - menuExtras;
}
//limit menuHeight to 300px to have a smooth transition with cubic bezier on dropdown
if(menuHeight >= 300){
menuHeight = 300;
}
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'auto', 'min-height' : liHeight * 3 + 'px'});
}, 50);
getSize;
$(window).on('scroll', getSize);
$(window).on('resize', getSize);
if (window.MutationObserver) {
new MutationObserver(getSize).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', getSize);
}
} else if (this.options.size && this.options.size != 'auto' && menu.find('li').length > this.options.size) {
var optIndex = menu.find("li > *").filter(':not(.divider)').slice(0,this.options.size).last().parent().index();
var divLength = menu.find("li").slice(0,optIndex + 1).find('.divider').length;
menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding;
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'scroll'});
//console.log('sunt in if');
}
// Listen for updates to the DOM and re render... (Use Mutation Observer when availiable)
if (window.MutationObserver) {
new MutationObserver($.proxy(this.reloadLi, this)).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', $.proxy(this.reloadLi, this));
}
this.render();
},
createDropdown: function() {
var drop =
"<div class='btn-group select'>" +
"<button class='btn dropdown-toggle clearfix' data-toggle='dropdown'>" +
"<span class='filter-option'></span>&nbsp;" +
"<span class='caret'></span>" +
"</button>" +
"<span class='dropdown-arrow'></span>" +
"<ul class='dropdown-menu' role='menu'>" +
"</ul>" +
"</div>";
return $(drop);
},
createView: function() {
var $drop = this.createDropdown();
var $li = this.createLi();
$drop.find('ul').append($li);
return $drop;
},
reloadLi: function() {
//Remove all children.
this.destroyLi();
//Re build
$li = this.createLi();
this.$newElement.find('ul').append( $li );
//render view
this.render();
},
destroyLi:function() {
this.$newElement.find('li').remove();
},
createLi: function() {
var _this = this;
var _li = [];
var _liA = [];
var _liHtml = '';
this.$element.find('option').each(function(){
_li.push($(this).text());
});
this.$element.find('option').each(function(index) {
//Get the class and text for the option
var optionClass = $(this).attr("class") !== undefined ? $(this).attr("class") : '';
var text = $(this).text();
var subtext = $(this).data('subtext') !== undefined ? '<small class="muted">'+$(this).data('subtext')+'</small>' : '';
//Append any subtext to the main text.
text+=subtext;
if ($(this).parent().is('optgroup') && $(this).data('divider') != true) {
if ($(this).index() == 0) {
//Get the opt group label
var label = $(this).parent().attr('label');
var labelSubtext = $(this).parent().data('subtext') !== undefined ? '<small class="muted">'+$(this).parent().data('subtext')+'</small>' : '';
label += labelSubtext;
if ($(this)[0].index != 0) {
_liA.push(
'<div class="divider"></div>'+
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass )
);
} else {
_liA.push(
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass ));
}
} else {
_liA.push( _this.createA(text, "opt " + optionClass ) );
}
} else if ($(this).data('divider') == true) {
_liA.push('<div class="divider"></div>');
} else if ($(this).data('hidden') == true) {
_liA.push('');
} else {
_liA.push( _this.createA(text, optionClass ) );
}
});
if (_li.length > 0) {
for (var i = 0; i < _li.length; i++) {
var $option = this.$element.find('option').eq(i);
_liHtml += "<li rel=" + i + ">" + _liA[i] + "</li>";
}
}
//If we dont have a selected item, and we dont have a title, select the first element so something is set in the button
if(this.$element.find('option:selected').length==0 && !_this.options.title) {
this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
}
return $(_liHtml);
},
createA:function(test, classes) {
return '<a tabindex="-1" href="#" class="'+classes+'">' +
'<span class="">' + test + '</span>' +
'</a>';
},
render:function() {
var _this = this;
//Set width of select
if (this.options.width == 'auto') {
var ulWidth = this.$newElement.find('.dropdown-menu').css('width');
this.$newElement.css('width',ulWidth);
} else if (this.options.width && this.options.width != 'auto') {
this.$newElement.css('width',this.options.width);
}
//Update the LI to match the SELECT
this.$element.find('option').each(function(index) {
_this.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') );
_this.setSelected(index, $(this).is(':selected') );
});
var selectedItems = this.$element.find('option:selected').map(function(index,value) {
if($(this).attr('title')!=undefined) {
return $(this).attr('title');
} else {
return $(this).text();
}
}).toArray();
//Convert all the values into a comma delimited string
var title = selectedItems.join(", ");
//If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
if(_this.multiple && _this.options.selectedTextFormat.indexOf('count') > -1) {
var max = _this.options.selectedTextFormat.split(">");
if( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) {
title = selectedItems.length +' of ' + this.$element.find('option').length + ' selected';
}
}
//If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
if(!title) {
title = _this.options.title != undefined ? _this.options.title : _this.options.noneSelectedText;
}
this.$element.next('.select').find('.filter-option').html( title );
},
setSelected:function(index, selected) {
if(selected) {
this.$newElement.find('li').eq(index).addClass('selected');
} else {
this.$newElement.find('li').eq(index).removeClass('selected');
}
},
setDisabled:function(index, disabled) {
if(disabled) {
this.$newElement.find('li').eq(index).addClass('disabled');
} else {
this.$newElement.find('li').eq(index).removeClass('disabled');
}
},
checkDisabled: function() {
if (this.$element.is(':disabled')) {
this.button.addClass('disabled');
this.button.click(function(e) {
e.preventDefault();
});
}
},
checkTabIndex: function() {
if (this.$element.is('[tabindex]')) {
var tabindex = this.$element.attr("tabindex");
this.button.attr('tabindex', tabindex);
}
},
clickListener: function() {
var _this = this;
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
this.$newElement.on('click', 'li a', function(e){
var clickedIndex = $(this).parent().index(),
$this = $(this).parent(),
$select = $this.parents('.select');
//Dont close on multi choice menu
if(_this.multiple) {
e.stopPropagation();
}
e.preventDefault();
//Dont run if we have been disabled
if ($select.prev('select').not(':disabled') && !$(this).parent().hasClass('disabled')){
//Deselect all others if not multi select box
if (!_this.multiple) {
$select.prev('select').find('option').removeAttr('selected');
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
//Else toggle the one we have chosen if we are multi selet.
else {
var selected = $select.prev('select').find('option').eq(clickedIndex).prop('selected');
if(selected) {
$select.prev('select').find('option').eq(clickedIndex).removeAttr('selected');
} else {
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
}
$select.find('.filter-option').html($this.text());
$select.find('button').focus();
// Trigger select 'change'
$select.prev('select').trigger('change');
}
});
this.$newElement.on('click', 'li.disabled a, li dt, li .divider', function(e) {
e.preventDefault();
e.stopPropagation();
$select = $(this).parent().parents('.select');
$select.find('button').focus();
});
this.$element.on('change', function(e) {
_this.render();
});
},
val:function(value) {
if(value!=undefined) {
this.$element.val( value );
this.$element.trigger('change');
return this.$element;
} else {
return this.$element.val();
}
}
};
$.fn.selectpicker = function(option, event) {
//get the args of the outer function..
var args = arguments;
var value;
var chain = this.each(function () {
var $this = $(this),
data = $this.data('selectpicker'),
options = typeof option == 'object' && option;
if (!data) {
$this.data('selectpicker', (data = new Selectpicker(this, options, event)));
} else {
for(var i in option) {
data[i]=option[i];
}
}
if (typeof option == 'string') {
//Copy the value of option, as once we shift the arguments
//it also shifts the value of option.
property = option;
if(data[property] instanceof Function) {
[].shift.apply(args);
value = data[property].apply(data, args);
} else {
value = data[property];
}
}
});
if(value!=undefined) {
return value;
} else {
return chain;
}
};
$.fn.selectpicker.defaults = {
style: null,
size: 'auto',
title: null,
selectedTextFormat : 'values',
noneSelectedText : 'Nothing selected',
width: null,
menuStyle: null,
toggleSize: null
}
}(window.jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,154 +1,154 @@
/*!
=========================================================
* 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.
*/
var searchVisible = 0;
var transparent = true;
var transparentDemo = true;
var fixedTop = false;
var navbar_initialized = false;
$(document).ready(function(){
window_width = $(window).width();
// check if there is an image set for the sidebar's background
lbd.checkSidebarImage();
// Init navigation toggle for small screens
lbd.initRightMenu();
// Activate the tooltips
$('[rel="tooltip"]').tooltip();
$('.form-control').on("focus", function(){
$(this).parent('.input-group').addClass("input-group-focus");
}).on("blur", function(){
$(this).parent(".input-group").removeClass("input-group-focus");
});
// Fixes sub-nav not working as expected on IOS
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
});
$(document).on('click', '.navbar-toggle', function(){
$toggle = $(this);
if(lbd.misc.navbar_menu_visible == 1) {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 550);
} else {
setTimeout(function(){
$toggle.addClass('toggled');
}, 580);
div = '<div id="bodyClick"></div>';
$(div).appendTo('body').click(function() {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
setTimeout(function(){
$toggle.removeClass('toggled');
$('#bodyClick').remove();
}, 550);
});
$('html').addClass('nav-open');
lbd.misc.navbar_menu_visible = 1;
}
});
$(window).on('resize', function(){
if(navbar_initialized){
lbd.initRightMenu();
navbar_initialized = true;
}
});
lbd = {
misc:{
navbar_menu_visible: 0
},
checkSidebarImage: function(){
$sidebar = $('.sidebar');
image_src = $sidebar.data('image');
if(image_src !== undefined){
sidebar_container = '<div class="sidebar-background" style="background-image: url(' + image_src + ') "/>'
$sidebar.append(sidebar_container);
}
},
initRightMenu: debounce(function(){
if(!navbar_initialized){
$sidebar_wrapper = $('.sidebar-wrapper');
$navbar = $('nav').find('.navbar-collapse').html();
mobile_menu_content = '';
nav_content = $navbar;
nav_content = '<ul class="nav nav-mobile-menu">' + nav_content + '</ul>';
// navbar_form = $('nav').find('.navbar-form').get(0).outerHTML;
$sidebar_nav = $sidebar_wrapper.find(' > .nav');
// insert the navbar form before the sidebar list
$nav_content = $(nav_content);
// $navbar_form = $(navbar_form);
$nav_content.insertBefore($sidebar_nav);
// $navbar_form.insertBefore($nav_content);
$(".sidebar-wrapper .dropdown .dropdown-menu > li > a").click(function(event) {
event.stopPropagation();
});
mobile_menu_initialized = true;
} else {
if($(window).width() > 991){
// reset all the additions that we made for the sidebar wrapper only if the screen is bigger than 991px
// $sidebar_wrapper.find('.navbar-form').remove();
$sidebar_wrapper.find('.nav-mobile-menu').remove();
mobile_menu_initialized = false;
}
}
},200)
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
/*!
=========================================================
* 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.
*/
var searchVisible = 0;
var transparent = true;
var transparentDemo = true;
var fixedTop = false;
var navbar_initialized = false;
$(document).ready(function(){
window_width = $(window).width();
// check if there is an image set for the sidebar's background
lbd.checkSidebarImage();
// Init navigation toggle for small screens
lbd.initRightMenu();
// Activate the tooltips
$('[rel="tooltip"]').tooltip();
$('.form-control').on("focus", function(){
$(this).parent('.input-group').addClass("input-group-focus");
}).on("blur", function(){
$(this).parent(".input-group").removeClass("input-group-focus");
});
// Fixes sub-nav not working as expected on IOS
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
});
$(document).on('click', '.navbar-toggle', function(){
$toggle = $(this);
if(lbd.misc.navbar_menu_visible == 1) {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 550);
} else {
setTimeout(function(){
$toggle.addClass('toggled');
}, 580);
div = '<div id="bodyClick"></div>';
$(div).appendTo('body').click(function() {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
setTimeout(function(){
$toggle.removeClass('toggled');
$('#bodyClick').remove();
}, 550);
});
$('html').addClass('nav-open');
lbd.misc.navbar_menu_visible = 1;
}
});
$(window).on('resize', function(){
if(navbar_initialized){
lbd.initRightMenu();
navbar_initialized = true;
}
});
lbd = {
misc:{
navbar_menu_visible: 0
},
checkSidebarImage: function(){
$sidebar = $('.sidebar');
image_src = $sidebar.data('image');
if(image_src !== undefined){
sidebar_container = '<div class="sidebar-background" style="background-image: url(' + image_src + ') "/>'
$sidebar.append(sidebar_container);
}
},
initRightMenu: debounce(function(){
if(!navbar_initialized){
$sidebar_wrapper = $('.sidebar-wrapper');
$navbar = $('nav').find('.navbar-collapse').html();
mobile_menu_content = '';
nav_content = $navbar;
nav_content = '<ul class="nav nav-mobile-menu">' + nav_content + '</ul>';
// navbar_form = $('nav').find('.navbar-form').get(0).outerHTML;
$sidebar_nav = $sidebar_wrapper.find(' > .nav');
// insert the navbar form before the sidebar list
$nav_content = $(nav_content);
// $navbar_form = $(navbar_form);
$nav_content.insertBefore($sidebar_nav);
// $navbar_form.insertBefore($nav_content);
$(".sidebar-wrapper .dropdown .dropdown-menu > li > a").click(function(event) {
event.stopPropagation();
});
mobile_menu_initialized = true;
} else {
if($(window).width() > 991){
// reset all the additions that we made for the sidebar wrapper only if the screen is bigger than 991px
// $sidebar_wrapper.find('.navbar-form').remove();
$sidebar_wrapper.find('.nav-mobile-menu').remove();
mobile_menu_initialized = false;
}
}
},200)
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};

View File

@ -1,56 +1,56 @@
{{define "head"}}
<title>Tickets | A Discord Support Manager Bot</title>
<!-- Meta -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Management panel for the Discord Tickets bot">
<link rel="shortcut icon" href="/assets/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="/assets/img/favicon.ico" type="image/x-icon">
<!-- Custom CSS -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<link href="/assets/css/style.css" rel="stylesheet">
<style>
.avatar {
background: url("{{.avatar}}?size=32");
width: 28px;
height: 29px;
display: block;
background-size: cover;
border-radius: 50%;
}
</style>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="/assets/css/light-bootstrap-dashboard.css?v=1.4.0" rel="stylesheet"/>
<link href="/assets/css/animate.min.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
crossorigin="anonymous"></script>
<!-- Discord theme -->
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
<!-- Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<!-- GA -->
<script type="80be96f83bbfbba3d4097e23-text/javascript">
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-161945537-1', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js' type="80be96f83bbfbba3d4097e23-text/javascript"></script>
{{define "head"}}
<title>Tickets | A Discord Support Manager Bot</title>
<!-- Meta -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Management panel for the Discord Tickets bot">
<link rel="shortcut icon" href="/assets/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="/assets/img/favicon.ico" type="image/x-icon">
<!-- Custom CSS -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<link href="/assets/css/style.css" rel="stylesheet">
<style>
.avatar {
background: url("{{.avatar}}?size=32");
width: 28px;
height: 29px;
display: block;
background-size: cover;
border-radius: 50%;
}
</style>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="/assets/css/light-bootstrap-dashboard.css?v=1.4.0" rel="stylesheet"/>
<link href="/assets/css/animate.min.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
crossorigin="anonymous"></script>
<!-- Discord theme -->
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
<!-- Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<!-- GA -->
<script type="80be96f83bbfbba3d4097e23-text/javascript">
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-161945537-1', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js' type="80be96f83bbfbba3d4097e23-text/javascript"></script>
{{end}}

View File

@ -1,23 +1,23 @@
{{define "navbar"}}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/settings">Settings</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/logs/page/1">Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/blacklist">Blacklist</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/tickets">Ticket List</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/panels">Reaction Panels</a>
</li>
</ul>
</div>
</nav>
{{define "navbar"}}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/settings">Settings</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/logs/page/1">Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/blacklist">Blacklist</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/tickets">Ticket List</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/manage/{{.guildId}}/panels">Reaction Panels</a>
</li>
</ul>
</div>
</nav>
{{end}}

View File

@ -1,33 +1,33 @@
{{define "sidebar"}}
<div class="sidebar" data-color="orange" data-image="/assets/img/sidebar-2.jpg">
<div class="sidebar-wrapper">
<div class="logo">
<a href="https://ticketsbot.net" class="simple-text">
TicketsBot
</a>
</div>
<ul class="nav">
<li class="nav-item sidebar-bottom">
<a href="/">
<i class="fas fa-server"></i>
<p>Servers</p>
</a>
</li>
<li class="nav-item sidebar-bottom" style="bottom: 60px">
<a href="/logout">
<i class="fas fa-sign-out-alt"></i>
<p>Logout</p>
</a>
</li>
<li class="nav-item sidebar-bottom" style="bottom: 10px">
<a href="#">
<i class="avatar"></i>
<p>{{.name}}</p>
</a>
</li>
</ul>
</div>
</div>
{{define "sidebar"}}
<div class="sidebar" data-color="orange" data-image="/assets/img/sidebar-2.jpg">
<div class="sidebar-wrapper">
<div class="logo">
<a href="https://ticketsbot.net" class="simple-text">
TicketsBot
</a>
</div>
<ul class="nav">
<li class="nav-item sidebar-bottom">
<a href="/">
<i class="fas fa-server"></i>
<p>Servers</p>
</a>
</li>
<li class="nav-item sidebar-bottom" style="bottom: 60px">
<a href="/logout">
<i class="fas fa-sign-out-alt"></i>
<p>Logout</p>
</a>
</li>
<li class="nav-item sidebar-bottom" style="bottom: 10px">
<a href="#">
<i class="avatar"></i>
<p>{{.name}}</p>
</a>
</li>
</ul>
</div>
</div>
{{end}}

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
<div class="wrapper">
{{template "sidebar" .}}
<div class="main-panel">
{{template "content" .}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
<div class="wrapper">
{{template "sidebar" .}}
<div class="main-panel">
{{template "content" .}}
</div>
</div>
</body>
</html>

View File

@ -1,16 +1,16 @@
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
<div class="wrapper">
{{template "sidebar" .}}
<div class="main-panel">
{{template "navbar" .}}
{{template "content" .}}
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
<div class="wrapper">
{{template "sidebar" .}}
<div class="main-panel">
{{template "navbar" .}}
{{template "content" .}}
</div>
</div>
</body>
</html>

View File

@ -1,115 +1,115 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Blacklisted Users</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<div id="accordion">
<div class="card">
<div class="card-header collapsed filterCard" id="addBlacklistHeader" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<span class="align-middle" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<i class="fas fa-plus"></i> Add New User
</span>
</div>
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
<div class="card-body">
<form>
<div class="row">
<div class="col-md-3 pr-1">
<div class="form-group">
<label>Username</label>
<input name="username" type="text" class="form-control" placeholder="Username">
</div>
</div>
<div class="col-md-1 px-1">
<label>Discriminator</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<input name="discrim" type="text" class="form-control" placeholder="0000">
</div>
</div>
</div>
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="row">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>User ID</th>
<th>Username#Discrim</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{{range .blacklisted}}
<tr>
<td>{{.userId}}</td>
<td>{{.username}}#{{.discrim}}</td>
<td><a href="/manage/{{$.guildId}}/blacklist/remove/{{.userId}}?c={{$.csrf}}">Remove</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .userNotFound}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The user you specified couldn't be found
</div>
</div>
{{end}}
{{if .isStaff}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
You cannot blacklist a staff member
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Blacklisted Users</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<div id="accordion">
<div class="card">
<div class="card-header collapsed filterCard" id="addBlacklistHeader" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<span class="align-middle" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<i class="fas fa-plus"></i> Add New User
</span>
</div>
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
<div class="card-body">
<form>
<div class="row">
<div class="col-md-3 pr-1">
<div class="form-group">
<label>Username</label>
<input name="username" type="text" class="form-control" placeholder="Username">
</div>
</div>
<div class="col-md-1 px-1">
<label>Discriminator</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<input name="discrim" type="text" class="form-control" placeholder="0000">
</div>
</div>
</div>
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="row">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>User ID</th>
<th>Username#Discrim</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{{range .blacklisted}}
<tr>
<td>{{.userId}}</td>
<td>{{.username}}#{{.discrim}}</td>
<td><a href="/manage/{{$.guildId}}/blacklist/remove/{{.userId}}?c={{$.csrf}}">Remove</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .userNotFound}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The user you specified couldn't be found
</div>
</div>
{{end}}
{{if .isStaff}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
You cannot blacklist a staff member
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{end}}

View File

@ -1,46 +1,46 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Servers</h4>
{{if .empty}}
<p class="card-category">Select a server to manage below</p>
{{end}}
</div>
<div class="card-body">
{{if .empty}}
<p class="center-align" style="padding-top: 50px; font-size: 16px">
You are not the admin of any guilds that the bot is in. Click below to invite the bot:
<br/>
<a href="https://invite.ticketsbot.net">Invite</a>
</p>
{{else}}
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<th>Server Name</th>
</thead>
<tbody>
{{range .servers}}
<tr>
<td>
<a href="/manage/{{.Id}}/settings">
{{.Name}}
</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Servers</h4>
{{if .empty}}
<p class="card-category">Select a server to manage below</p>
{{end}}
</div>
<div class="card-body">
{{if .empty}}
<p class="center-align" style="padding-top: 50px; font-size: 16px">
You are not the admin of any guilds that the bot is in. Click below to invite the bot:
<br/>
<a href="https://invite.ticketsbot.net">Invite</a>
</p>
{{else}}
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<th>Server Name</th>
</thead>
<tbody>
{{range .servers}}
<tr>
<td>
<a href="/manage/{{.Id}}/settings">
{{.Name}}
</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
</div>
{{end}}

View File

@ -1,100 +1,100 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div id="accordion">
<div class="card">
<div class="card-header collapsed filterCard" id="filterHeader" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
<span class="align-middle" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
<i class="fas fa-search"></i> Filter Logs
</span>
</div>
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
<div class="card-body">
<form action="/manage/{{.guildId}}/logs/page/1">
<div class="row">
<div class="col-md-4 pr-1">
<div class="form-group">
<label>Ticket ID</label>
<input name="ticketid" type="text" class="form-control" placeholder="Ticket ID">
</div>
</div>
<div class="col-md-4 px-1">
<div class="form-group">
<label>Username</label>
<input name="username" type="text" class="form-control" placeholder="Username">
</div>
</div>
<div class="col-md-4 px-1">
<div class="form-group">
<label>User ID</label>
<input name="userid" type="text" class="form-control" placeholder="User ID">
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Filter</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Logs</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Ticket ID</th>
<th>Username</th>
<th>User ID</th>
<th>Log URL</th>
</tr>
</thead>
<tbody>
{{range .logs}}
<tr>
<td>{{.ticketid}}</td>
<td>{{.username}}</td>
<td>{{.userid}}</td>
<td><a href="/manage/{{$.guildId}}/logs/view/{{.uuid}}">{{.uuid}}</a></td>
</tr>
{{end}}
</tbody>
</table>
<div class="row">
<div class="col-md-12">
<ul class="pagination justify-content-center">
{{if .isPageOne}}
<li class="disabled"><a href="#"><i class="fas fa-chevron-left"></i></a></li>
{{else}}
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.previousPage}}"><i class="fas fa-chevron-left"></i></a></li>
{{end}}
<p class="center-align" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.nextPage}}"><i class="fas fa-chevron-right"></i></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div id="accordion">
<div class="card">
<div class="card-header collapsed filterCard" id="filterHeader" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
<span class="align-middle" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
<i class="fas fa-search"></i> Filter Logs
</span>
</div>
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
<div class="card-body">
<form action="/manage/{{.guildId}}/logs/page/1">
<div class="row">
<div class="col-md-4 pr-1">
<div class="form-group">
<label>Ticket ID</label>
<input name="ticketid" type="text" class="form-control" placeholder="Ticket ID">
</div>
</div>
<div class="col-md-4 px-1">
<div class="form-group">
<label>Username</label>
<input name="username" type="text" class="form-control" placeholder="Username">
</div>
</div>
<div class="col-md-4 px-1">
<div class="form-group">
<label>User ID</label>
<input name="userid" type="text" class="form-control" placeholder="User ID">
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Filter</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Logs</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Ticket ID</th>
<th>Username</th>
<th>User ID</th>
<th>Log URL</th>
</tr>
</thead>
<tbody>
{{range .logs}}
<tr>
<td>{{.ticketid}}</td>
<td>{{.username}}</td>
<td>{{.userid}}</td>
<td><a href="/manage/{{$.guildId}}/logs/view/{{.uuid}}">{{.uuid}}</a></td>
</tr>
{{end}}
</tbody>
</table>
<div class="row">
<div class="col-md-12">
<ul class="pagination justify-content-center">
{{if .isPageOne}}
<li class="disabled"><a href="#"><i class="fas fa-chevron-left"></i></a></li>
{{else}}
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.previousPage}}"><i class="fas fa-chevron-left"></i></a></li>
{{end}}
<p class="center-align" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.nextPage}}"><i class="fas fa-chevron-right"></i></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{end}}

View File

@ -1,235 +1,235 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Reaction Panels</h4>
</div>
<div class="card-body">
<p>Your panel quota: <b>{{.panelcount}} / {{if .premium}}∞{{else}}1{{end}}</b></p>
{{if not .premium}}
<p>Note: You can expand your panel quote by purchasing premium</p>
{{end}}
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Channel</th>
<th>Panel Title</th>
<th>Panel Content</th>
<th>Ticket Channel Category</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{{range .panels}}
<tr>
<td>#{{.ChannelName}}</td>
<td>{{.Title}}</td>
<td>{{.Content}}</td>
<td>{{.CategoryName}}</td>
<td><a href="/manage/{{$.guildId}}/panels/delete/{{.MessageId}}?csrf={{$.csrf}}">Delete</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<h4 class="card-title">Create A Panel</h4>
</div>
<div class="card-body">
<form method="post" action="/manage/{{.guildId}}/panels/create">
<div class="row">
<div class="col-md-4 pr-1">
<div class="form-group">
<label class="black">Panel Title</label>
<input name="title" type="text" class="form-control" placeholder="Open a ticket!">
</div>
</div>
<div class="col-md-8 pr-1">
<div class="form-group">
<label class="black">Panel Content</label>
<textarea name="content" type="text" class="form-control"
placeholder="By reacting to this ticket, a ticket will be opened for you."></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 pr-1">
<label class="black">Panel Colour</label>
<div class="input-group mb-3">
<input name="colour" type="color" class="form-control input-fill" value="#23A31A">
</div>
</div>
<div class="col-md-3 pr-1">
<label class="black">Panel Channel</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="channel">
{{range $id, $name := .channels}}
<option value="{{$id}}">{{$name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-3 pr-1">
<label class="black">Ticket Channel Category</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="categories">
{{range $id, $name := .categories}}
<option value="{{$id}}">{{$name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-3 pr-1">
<div class="form-group">
<label class="black">Reaction Emote</label>
<input name="reaction" type="text" class="form-control" placeholder="envelope_with_arrow">
</div>
</div>
</div>
<div class="row">
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="col-md-2 pr-1 offset-md-5">
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if not .validTitle}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Panel titles must be between 1 and 255 characters long
</div>
</div>
{{end}}
{{if not .validContent}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Panel content must be between 1 and 1024 characters long
</div>
</div>
{{end}}
{{if not .validColour}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid panel colour. You must use the hex value of the colour, which you can find <a href="https://www.google.co.uk/search?client=opera&q=html+colour+picker">here</a>.
<br />Colour has defaulted to green.
</div>
</div>
{{end}}
{{if not .validChannel}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid channel - please try again
</div>
</div>
{{end}}
{{if not .validCategory}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid category - please try again
</div>
</div>
{{end}}
{{if not .validReaction}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid reaction emote
</div>
</div>
{{end}}
{{if .created}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Success</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Your panel has been created. You may need to refresh this page to see it displayed.
</div>
</div>
{{end}}
{{if .metQuota}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
You've hit your panel quota. Premium users can create <b>unlimited panels</b>. Click <a href="https://ticketsbot.net/premium">here</a> to learn more about premium.
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Reaction Panels</h4>
</div>
<div class="card-body">
<p>Your panel quota: <b>{{.panelcount}} / {{if .premium}}∞{{else}}1{{end}}</b></p>
{{if not .premium}}
<p>Note: You can expand your panel quote by purchasing premium</p>
{{end}}
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Channel</th>
<th>Panel Title</th>
<th>Panel Content</th>
<th>Ticket Channel Category</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{{range .panels}}
<tr>
<td>#{{.ChannelName}}</td>
<td>{{.Title}}</td>
<td>{{.Content}}</td>
<td>{{.CategoryName}}</td>
<td><a href="/manage/{{$.guildId}}/panels/delete/{{.MessageId}}?csrf={{$.csrf}}">Delete</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<h4 class="card-title">Create A Panel</h4>
</div>
<div class="card-body">
<form method="post" action="/manage/{{.guildId}}/panels/create">
<div class="row">
<div class="col-md-4 pr-1">
<div class="form-group">
<label class="black">Panel Title</label>
<input name="title" type="text" class="form-control" placeholder="Open a ticket!">
</div>
</div>
<div class="col-md-8 pr-1">
<div class="form-group">
<label class="black">Panel Content</label>
<textarea name="content" type="text" class="form-control"
placeholder="By reacting to this ticket, a ticket will be opened for you."></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 pr-1">
<label class="black">Panel Colour</label>
<div class="input-group mb-3">
<input name="colour" type="color" class="form-control input-fill" value="#23A31A">
</div>
</div>
<div class="col-md-3 pr-1">
<label class="black">Panel Channel</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="channel">
{{range $id, $name := .channels}}
<option value="{{$id}}">{{$name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-3 pr-1">
<label class="black">Ticket Channel Category</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="categories">
{{range $id, $name := .categories}}
<option value="{{$id}}">{{$name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-3 pr-1">
<div class="form-group">
<label class="black">Reaction Emote</label>
<input name="reaction" type="text" class="form-control" placeholder="envelope_with_arrow">
</div>
</div>
</div>
<div class="row">
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="col-md-2 pr-1 offset-md-5">
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if not .validTitle}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Panel titles must be between 1 and 255 characters long
</div>
</div>
{{end}}
{{if not .validContent}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Panel content must be between 1 and 1024 characters long
</div>
</div>
{{end}}
{{if not .validColour}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid panel colour. You must use the hex value of the colour, which you can find <a href="https://www.google.co.uk/search?client=opera&q=html+colour+picker">here</a>.
<br />Colour has defaulted to green.
</div>
</div>
{{end}}
{{if not .validChannel}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid channel - please try again
</div>
</div>
{{end}}
{{if not .validCategory}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid category - please try again
</div>
</div>
{{end}}
{{if not .validReaction}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid reaction emote
</div>
</div>
{{end}}
{{if .created}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Success</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Your panel has been created. You may need to refresh this page to see it displayed.
</div>
</div>
{{end}}
{{if .metQuota}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
You've hit your panel quota. Premium users can create <b>unlimited panels</b>. Click <a href="https://ticketsbot.net/premium">here</a> to learn more about premium.
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{end}}

View File

@ -1,159 +1,159 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Settings</h4>
{{if .empty}}
<p class="card-category">Select a server to manage below</p>
{{end}}
</div>
<div class="card-body">
<form method="post">
<div class="row">
<div class="col-md-5 pr-1">
<div class="form-group">
<label>Prefix (Max len. 8)</label>
<input name="prefix" type="text" class="form-control" placeholder="t!" value="{{.prefix}}">
</div>
</div>
<div class="col-md-5 px-1">
<div class="form-group">
<label>Ticket Limit (1-10)</label>
<input name="ticketlimit" type="text" class="form-control" placeholder="5" value="{{.ticketLimit}}">
</div>
</div>
<div class="col-md-2 px-1">
<div class="form-group">
<label>Ping @everyone on ticket open</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="pingeveryone" value="on" {{if .pingEveryone}}checked{{end}} style="width:30px;height:30px;">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label>Welcome Message (Max len. 1000)</label>
<textarea name="welcomeMessage" class="form-control" rows="3" style="resize: none">{{.welcomeMessage}}</textarea>
</div>
</div>
<div class="row">
<div class="col-md-5 pr-1">
<label>Archive Channel</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="archivechannel">
{{range .channels}}
<option {{if eq .ChannelId $.archivechannel }}selected{{end}} value="{{.ChannelId}}">{{.Name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-5 px-1">
<div class="form-group">
<label>Channel Category</label>
<select class="form-control" name="category">
{{range .categories}}
<option {{if eq $.activecategory .ChannelId}}selected{{end}} value="{{.ChannelId}}">{{.Name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-2 px-1">
<div class="form-group">
<label>Allow users to close tickets</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="userscanclose" value="on" {{if .usersCanClose}}checked{{end}} style="width:30px;height:30px;">
</div>
</div>
</div>
</div>
<label>Ticket Naming Scheme</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-id" value="id" {{if eq .namingScheme "id"}}checked{{end}}>
<label class="form-check-label" for="naming-by-id">
Ticket ID
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-username" value="username" {{if eq .namingScheme "username"}}checked{{end}}>
<label class="form-check-label" for="naming-by-username">
Username
</label>
</div>
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="row">
<div class="col-md-1 pr-1">
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .invalidPrefix}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The prefix you specified was invalid
</div>
</div>
{{end}}
{{if .invalidWelcomeMessage}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The welcome message you specified was invalid
</div>
</div>
{{end}}
{{if .invalidTicketLimit}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The ticket limit you specified was invalid
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
$('#pingeveryone').prop('indeterminate', {{.pingEveryone}});
</script>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Settings</h4>
{{if .empty}}
<p class="card-category">Select a server to manage below</p>
{{end}}
</div>
<div class="card-body">
<form method="post">
<div class="row">
<div class="col-md-5 pr-1">
<div class="form-group">
<label>Prefix (Max len. 8)</label>
<input name="prefix" type="text" class="form-control" placeholder="t!" value="{{.prefix}}">
</div>
</div>
<div class="col-md-5 px-1">
<div class="form-group">
<label>Ticket Limit (1-10)</label>
<input name="ticketlimit" type="text" class="form-control" placeholder="5" value="{{.ticketLimit}}">
</div>
</div>
<div class="col-md-2 px-1">
<div class="form-group">
<label>Ping @everyone on ticket open</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="pingeveryone" value="on" {{if .pingEveryone}}checked{{end}} style="width:30px;height:30px;">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label>Welcome Message (Max len. 1000)</label>
<textarea name="welcomeMessage" class="form-control" rows="3" style="resize: none">{{.welcomeMessage}}</textarea>
</div>
</div>
<div class="row">
<div class="col-md-5 pr-1">
<label>Archive Channel</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<select class="form-control" name="archivechannel">
{{range .channels}}
<option {{if eq .ChannelId $.archivechannel }}selected{{end}} value="{{.ChannelId}}">{{.Name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-5 px-1">
<div class="form-group">
<label>Channel Category</label>
<select class="form-control" name="category">
{{range .categories}}
<option {{if eq $.activecategory .ChannelId}}selected{{end}} value="{{.ChannelId}}">{{.Name}}</option>
{{end}}
</select>
</div>
</div>
<div class="col-md-2 px-1">
<div class="form-group">
<label>Allow users to close tickets</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="userscanclose" value="on" {{if .usersCanClose}}checked{{end}} style="width:30px;height:30px;">
</div>
</div>
</div>
</div>
<label>Ticket Naming Scheme</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-id" value="id" {{if eq .namingScheme "id"}}checked{{end}}>
<label class="form-check-label" for="naming-by-id">
Ticket ID
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-username" value="username" {{if eq .namingScheme "username"}}checked{{end}}>
<label class="form-check-label" for="naming-by-username">
Username
</label>
</div>
<input name="csrf" type="hidden" value="{{.csrf}}">
<div class="row">
<div class="col-md-1 pr-1">
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .invalidPrefix}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The prefix you specified was invalid
</div>
</div>
{{end}}
{{if .invalidWelcomeMessage}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The welcome message you specified was invalid
</div>
</div>
{{end}}
{{if .invalidTicketLimit}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The ticket limit you specified was invalid
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
$('#pingeveryone').prop('indeterminate', {{.pingEveryone}});
</script>
</div>
{{end}}

View File

@ -1,43 +1,43 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Ticket List</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Ticket ID</th>
<th>User</th>
<th>Additional Members</th>
<th>View</th>
</tr>
</thead>
<tbody>
{{range .tickets}}
<tr>
<td>{{.ticketId}}</td>
<td>{{.username}}#{{.discrim}}</td>
<td>{{range .members}}{{.username}}#{{.discrim}}{{.sep}}{{end}}</td>
<td><a class="btn btn-primary btn-sm" role="button" href="/manage/{{$.guildId}}/tickets/view/{{.uuid}}">View</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Ticket List</h4>
</div>
<div class="card-body">
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Ticket ID</th>
<th>User</th>
<th>Additional Members</th>
<th>View</th>
</tr>
</thead>
<tbody>
{{range .tickets}}
<tr>
<td>{{.ticketId}}</td>
<td>{{.username}}#{{.discrim}}</td>
<td>{{range .members}}{{.username}}#{{.discrim}}{{.sep}}{{end}}</td>
<td><a class="btn btn-primary btn-sm" role="button" href="/manage/{{$.guildId}}/tickets/view/{{.uuid}}">View</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
{{end}}

View File

@ -1,122 +1,122 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Close Ticket</h4>
<div class="close-container">
<form class="form-inline" action="/manage/{{.guildId}}/tickets/view/{{.uuid}}/close" method="post">
<input type="text" class="form-control" id="reason" name="reason" placeholder="No reason specified" style="width: 80%">
<input name="csrf" type="hidden" value="{{.csrf}}">
<div style="padding-left: 10px">
<button type="submit" class="btn btn-primary">Close Ticket</button>
</div>
</form>
</div>
<h4 class="card-title">View Ticket</h4>
<div class="discord-container">
<div class="channel-header">
<span class="channel-name">#ticket-{{.ticketId}}</span>
</div>
<div id="message-container">
{{range .messages}}
<div class="message">
<b>{{.username}}</b>
{{.content}}
</div>
{{end}}
</div>
<div class="input-container">
<form action="javascript:sendMessage()">
{{if .premium}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Message #ticket-{{.ticketId}}">
{{else}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Premium users get live messages and can respond through webchat" disabled>
{{end}}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .isError}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
{{.error}}
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
</div>
<script>
// Scroll to bottom
let container = document.getElementById("message-container");
container.scrollTop = container.scrollHeight;
</script>
{{if .premium}}
<script>
let ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
ws.onopen = () => {
ws.send(JSON.stringify({
"type": "auth",
"data": {
"guild": "{{.guildId}}",
"ticket": "{{.ticketId}}"
}
}));
};
ws.onmessage = (evt) => {
let data = JSON.parse(evt.data);
let container = document.getElementById("message-container");
let element = document.createElement("div");
element.className = "message";
element.innerHTML = `
<b>${data.username}</b>
${data.content}
`;
container.appendChild(element);
// Scroll to bottom
container.scrollTop = container.scrollHeight;
};
function sendMessage() {
let msg = document.getElementById("message").value;
document.getElementById("message").value = "";
ws.send(JSON.stringify({
"type": "send",
"data": msg
}))
}
</script>
{{end}}
{{end}}
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Close Ticket</h4>
<div class="close-container">
<form class="form-inline" action="/manage/{{.guildId}}/tickets/view/{{.uuid}}/close" method="post">
<input type="text" class="form-control" id="reason" name="reason" placeholder="No reason specified" style="width: 80%">
<input name="csrf" type="hidden" value="{{.csrf}}">
<div style="padding-left: 10px">
<button type="submit" class="btn btn-primary">Close Ticket</button>
</div>
</form>
</div>
<h4 class="card-title">View Ticket</h4>
<div class="discord-container">
<div class="channel-header">
<span class="channel-name">#ticket-{{.ticketId}}</span>
</div>
<div id="message-container">
{{range .messages}}
<div class="message">
<b>{{.username}}</b>
{{.content}}
</div>
{{end}}
</div>
<div class="input-container">
<form action="javascript:sendMessage()">
{{if .premium}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Message #ticket-{{.ticketId}}">
{{else}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Premium users get live messages and can respond through webchat" disabled>
{{end}}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{if .isError}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
{{.error}}
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
</div>
<script>
// Scroll to bottom
let container = document.getElementById("message-container");
container.scrollTop = container.scrollHeight;
</script>
{{if .premium}}
<script>
let ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
ws.onopen = () => {
ws.send(JSON.stringify({
"type": "auth",
"data": {
"guild": "{{.guildId}}",
"ticket": "{{.ticketId}}"
}
}));
};
ws.onmessage = (evt) => {
let data = JSON.parse(evt.data);
let container = document.getElementById("message-container");
let element = document.createElement("div");
element.className = "message";
element.innerHTML = `
<b>${data.username}</b>
${data.content}
`;
container.appendChild(element);
// Scroll to bottom
container.scrollTop = container.scrollHeight;
};
function sendMessage() {
let msg = document.getElementById("message").value;
document.getElementById("message").value = "";
ws.send(JSON.stringify({
"type": "send",
"data": msg
}))
}
</script>
{{end}}
{{end}}

View File

@ -1,119 +1,119 @@
package discord
import (
"bytes"
"encoding/json"
"github.com/TicketsBot/GoPanel/config"
"github.com/pasztorpisti/qs"
"io/ioutil"
"net/http"
"strconv"
"time"
)
type (
TokenData struct {
ClientId string `qs:"client_id"`
ClientSecret string `qs:"client_secret"`
GrantType string `qs:"grant_type"`
Code string `qs:"code"`
RedirectUri string `qs:"redirect_uri"`
Scope string `qs:"scope"`
}
RefreshData struct {
ClientId string `qs:"client_id"`
ClientSecret string `qs:"client_secret"`
GrantType string `qs:"grant_type"`
RefreshToken string `qs:"refresh_token"`
RedirectUri string `qs:"redirect_uri"`
Scope string `qs:"scope"`
}
TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
}
)
const TokenEndpoint = "https://discordapp.com/api/oauth2/token"
func AccessToken(code string) (TokenResponse, error) {
data := TokenData{
ClientId: strconv.Itoa(int(config.Conf.Oauth.Id)),
ClientSecret: config.Conf.Oauth.Secret,
GrantType: "authorization_code",
Code: code,
RedirectUri: config.Conf.Oauth.RedirectUri,
Scope: "identify guilds",
}
res, err := tokenPost(data)
if err != nil {
return TokenResponse{}, err
}
var unmarshalled TokenResponse
if err = json.Unmarshal(res, &unmarshalled); err != nil {
return TokenResponse{}, err
}
return unmarshalled, nil
}
func RefreshToken(refreshToken string) (TokenResponse, error) {
data := RefreshData{
ClientId: strconv.Itoa(int(config.Conf.Oauth.Id)),
ClientSecret: config.Conf.Oauth.Secret,
GrantType: "refresh_token",
RefreshToken: refreshToken,
RedirectUri: config.Conf.Oauth.RedirectUri,
Scope: "identify guilds",
}
res, err := tokenPost(data)
if err != nil {
return TokenResponse{}, err
}
var unmarshalled TokenResponse
if err = json.Unmarshal(res, &unmarshalled); err != nil {
return TokenResponse{}, err
}
return unmarshalled, nil
}
func tokenPost(body ...interface{}) ([]byte, error) {
str, err := qs.Marshal(body[0])
if err != nil {
return nil, err
}
encoded := []byte(str)
req, err := http.NewRequest("POST", TokenEndpoint, bytes.NewBuffer([]byte(encoded)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", string(ApplicationFormUrlEncoded))
client := &http.Client{}
client.Timeout = 3 * time.Second
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return content, nil
}
package discord
import (
"bytes"
"encoding/json"
"github.com/TicketsBot/GoPanel/config"
"github.com/pasztorpisti/qs"
"io/ioutil"
"net/http"
"strconv"
"time"
)
type (
TokenData struct {
ClientId string `qs:"client_id"`
ClientSecret string `qs:"client_secret"`
GrantType string `qs:"grant_type"`
Code string `qs:"code"`
RedirectUri string `qs:"redirect_uri"`
Scope string `qs:"scope"`
}
RefreshData struct {
ClientId string `qs:"client_id"`
ClientSecret string `qs:"client_secret"`
GrantType string `qs:"grant_type"`
RefreshToken string `qs:"refresh_token"`
RedirectUri string `qs:"redirect_uri"`
Scope string `qs:"scope"`
}
TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
}
)
const TokenEndpoint = "https://discordapp.com/api/oauth2/token"
func AccessToken(code string) (TokenResponse, error) {
data := TokenData{
ClientId: strconv.Itoa(int(config.Conf.Oauth.Id)),
ClientSecret: config.Conf.Oauth.Secret,
GrantType: "authorization_code",
Code: code,
RedirectUri: config.Conf.Oauth.RedirectUri,
Scope: "identify guilds",
}
res, err := tokenPost(data)
if err != nil {
return TokenResponse{}, err
}
var unmarshalled TokenResponse
if err = json.Unmarshal(res, &unmarshalled); err != nil {
return TokenResponse{}, err
}
return unmarshalled, nil
}
func RefreshToken(refreshToken string) (TokenResponse, error) {
data := RefreshData{
ClientId: strconv.Itoa(int(config.Conf.Oauth.Id)),
ClientSecret: config.Conf.Oauth.Secret,
GrantType: "refresh_token",
RefreshToken: refreshToken,
RedirectUri: config.Conf.Oauth.RedirectUri,
Scope: "identify guilds",
}
res, err := tokenPost(data)
if err != nil {
return TokenResponse{}, err
}
var unmarshalled TokenResponse
if err = json.Unmarshal(res, &unmarshalled); err != nil {
return TokenResponse{}, err
}
return unmarshalled, nil
}
func tokenPost(body ...interface{}) ([]byte, error) {
str, err := qs.Marshal(body[0])
if err != nil {
return nil, err
}
encoded := []byte(str)
req, err := http.NewRequest("POST", TokenEndpoint, bytes.NewBuffer([]byte(encoded)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", string(ApplicationFormUrlEncoded))
client := &http.Client{}
client.Timeout = 3 * time.Second
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return content, nil
}

View File

@ -1,14 +1,14 @@
package channel
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
)
func GetChannelMessages(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/channels/%d/messages", id),
}
}
package channel
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
)
func GetChannelMessages(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/channels/%d/messages", id),
}
}

View File

@ -1,15 +1,15 @@
package guild
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
"strconv"
)
func GetGuild(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/guilds/%s", strconv.Itoa(id)),
}
}
package guild
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
"strconv"
)
func GetGuild(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/guilds/%s", strconv.Itoa(id)),
}
}

View File

@ -1,14 +1,14 @@
package guild
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
)
func GetGuildChannels(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/guilds/%d/channels", id),
}
}
package guild
import (
"fmt"
"github.com/TicketsBot/GoPanel/utils/discord"
)
func GetGuildChannels(id int) discord.Endpoint {
return discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BOT,
Endpoint: fmt.Sprintf("/guilds/%d/channels", id),
}
}

View File

@ -1,9 +1,9 @@
package user
import "github.com/TicketsBot/GoPanel/utils/discord"
var CurrentUser = discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BEARER,
Endpoint: "/users/@me",
}
package user
import "github.com/TicketsBot/GoPanel/utils/discord"
var CurrentUser = discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BEARER,
Endpoint: "/users/@me",
}

View File

@ -1,9 +1,9 @@
package user
import "github.com/TicketsBot/GoPanel/utils/discord"
var CurrentUserGuilds = discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BEARER,
Endpoint: "/users/@me/guilds",
}
package user
import "github.com/TicketsBot/GoPanel/utils/discord"
var CurrentUserGuilds = discord.Endpoint{
RequestType: discord.GET,
AuthorizationType: discord.BEARER,
Endpoint: "/users/@me/guilds",
}

View File

@ -1,16 +1,16 @@
package objects
type Activity struct {
Name string
Type int
Url string
Timestamps Timestamp
ApplicationId string
Details string
State string
Party Party
Assets Asset
Secrets Secret
Instance bool
Flags int
}
package objects
type Activity struct {
Name string
Type int
Url string
Timestamps Timestamp
ApplicationId string
Details string
State string
Party Party
Assets Asset
Secrets Secret
Instance bool
Flags int
}

View File

@ -1,8 +1,8 @@
package objects
type Asset struct {
LargeImage string
LargeText string
SmallImage string
SmallText string
}
package objects
type Asset struct {
LargeImage string
LargeText string
SmallImage string
SmallText string
}

View File

@ -1,11 +1,11 @@
package objects
type Attachment struct {
Id string
Filename string
Size int
url string
ProxyUrl string
height int
Width int
}
package objects
type Attachment struct {
Id string
Filename string
Size int
url string
ProxyUrl string
height int
Width int
}

View File

@ -1,22 +1,22 @@
package objects
type Channel struct {
Id string
Type int
GuildId string
Position int
PermissionsOverwrites []Overwrite
Name string
Topic string
Nsfw bool
LastMessageId string
Bitrate int
userLimit int
RateLimitPerUser int
Recipients []User
Icon string
Ownerid string
ApplicationId string
ParentId string
LastPinTimestamp string
}
package objects
type Channel struct {
Id string
Type int
GuildId string
Position int
PermissionsOverwrites []Overwrite
Name string
Topic string
Nsfw bool
LastMessageId string
Bitrate int
userLimit int
RateLimitPerUser int
Recipients []User
Icon string
Ownerid string
ApplicationId string
ParentId string
LastPinTimestamp string
}

View File

@ -1,7 +1,7 @@
package objects
type ClientStatus struct {
Desktop string
Mobile string
Web string
}
package objects
type ClientStatus struct {
Desktop string
Mobile string
Web string
}

View File

@ -1,17 +1,17 @@
package objects
type Embed struct {
Title string
Type string
Description string
Url string
Timestamp string
Color int
Footer EmbedField
Image EmbedImage
Thumbnail EmbedThumbnail
Video EmbedVideo
Provider EmbedProvider
Author EmbedAuthor
Fields []EmbedField
}
package objects
type Embed struct {
Title string
Type string
Description string
Url string
Timestamp string
Color int
Footer EmbedField
Image EmbedImage
Thumbnail EmbedThumbnail
Video EmbedVideo
Provider EmbedProvider
Author EmbedAuthor
Fields []EmbedField
}

View File

@ -1,8 +1,8 @@
package objects
type EmbedAuthor struct {
Name string
Url string
IconUrl string
ProxyIconUrl string
}
package objects
type EmbedAuthor struct {
Name string
Url string
IconUrl string
ProxyIconUrl string
}

View File

@ -1,7 +1,7 @@
package objects
type EmbedField struct {
Name string
Value string
Inline bool
}
package objects
type EmbedField struct {
Name string
Value string
Inline bool
}

View File

@ -1,7 +1,7 @@
package objects
type EmbedFooter struct {
Text string
IconUrl string
ProxyIconUrl string
}
package objects
type EmbedFooter struct {
Text string
IconUrl string
ProxyIconUrl string
}

View File

@ -1,8 +1,8 @@
package objects
type EmbedImage struct {
Url string
ProxyUrl string
Height int
Width int
}
package objects
type EmbedImage struct {
Url string
ProxyUrl string
Height int
Width int
}

View File

@ -1,6 +1,6 @@
package objects
type EmbedProvider struct {
Name string
Url string
}
package objects
type EmbedProvider struct {
Name string
Url string
}

View File

@ -1,8 +1,8 @@
package objects
type EmbedThumbnail struct {
Url string
ProxyUrl string
Height int
Width int
}
package objects
type EmbedThumbnail struct {
Url string
ProxyUrl string
Height int
Width int
}

View File

@ -1,7 +1,7 @@
package objects
type EmbedVideo struct {
Url string
Height int
Width int
}
package objects
type EmbedVideo struct {
Url string
Height int
Width int
}

View File

@ -1,11 +1,11 @@
package objects
type Emoji struct {
Id string
Name string
Roles []string
User User
RequireColons bool
Managed bool
Animated bool
}
package objects
type Emoji struct {
Id string
Name string
Roles []string
User User
RequireColons bool
Managed bool
Animated bool
}

View File

@ -1,25 +1,25 @@
package objects
type Message struct {
Id string
ChannelId string
GuildId string
Author User
Member Member
Content string
Timestamp string
EditedTimestamp string
Tts bool
MentionEveryone bool
Mentions []interface{}
MentionsRoles []int64
Attachments []Attachment
Embeds []Embed
Reactions []Reaction
Nonce string
Pinned bool
WebhookId string
Type int
Activity MessageActivity
Application MessageApplication
}
package objects
type Message struct {
Id string
ChannelId string
GuildId string
Author User
Member Member
Content string
Timestamp string
EditedTimestamp string
Tts bool
MentionEveryone bool
Mentions []interface{}
MentionsRoles []int64
Attachments []Attachment
Embeds []Embed
Reactions []Reaction
Nonce string
Pinned bool
WebhookId string
Type int
Activity MessageActivity
Application MessageApplication
}

View File

@ -1,6 +1,6 @@
package objects
type MessageActivity struct {
Type int
PartyId string
}
package objects
type MessageActivity struct {
Type int
PartyId string
}

View File

@ -1,9 +1,9 @@
package objects
type MessageApplication struct {
Id string
CoverImage string
Description string
Icon string
Name string
}
package objects
type MessageApplication struct {
Id string
CoverImage string
Description string
Icon string
Name string
}

View File

@ -1,8 +1,8 @@
package objects
type Overwrite struct {
Id string
Type string
Allow int
Deny int
}
package objects
type Overwrite struct {
Id string
Type string
Allow int
Deny int
}

View File

@ -1,6 +1,6 @@
package objects
type Party struct {
Id string
Size []int
}
package objects
type Party struct {
Id string
Size []int
}

View File

@ -1,11 +1,11 @@
package objects
type Presence struct {
User User
Roles []string
Game Activity
GuildId string
Status string
Activities []Activity
ClientStatus ClientStatus
}
package objects
type Presence struct {
User User
Roles []string
Game Activity
GuildId string
Status string
Activities []Activity
ClientStatus ClientStatus
}

View File

@ -1,7 +1,7 @@
package objects
type Reaction struct {
Count int
Me bool
Emoji Emoji
}
package objects
type Reaction struct {
Count int
Me bool
Emoji Emoji
}

View File

@ -1,12 +1,12 @@
package objects
type Role struct {
Id string
Name string
Color int
Hoist bool
Position int
Permissions int
Managed bool
Mentionable bool
}
package objects
type Role struct {
Id string
Name string
Color int
Hoist bool
Position int
Permissions int
Managed bool
Mentionable bool
}

View File

@ -1,7 +1,7 @@
package objects
type Secret struct {
Join string
Spectate string
Match string
}
package objects
type Secret struct {
Join string
Spectate string
Match string
}

View File

@ -1,6 +1,6 @@
package objects
type Timestamp struct {
Start int
End int
}
package objects
type Timestamp struct {
Start int
End int
}

View File

@ -1,12 +1,12 @@
package objects
type User struct {
Id string
Username string
Discriminator string
Avatar string
Verified bool
Email string
Flags int
PremiumType int
}
package objects
type User struct {
Id string
Username string
Discriminator string
Avatar string
Verified bool
Email string
Flags int
PremiumType int
}

View File

@ -1,14 +1,14 @@
package objects
type VoiceState struct {
GuildId string
ChannelId string
UserId string
Member Member
SessionId string
Deaf bool
Mute bool
SelfDeaf bool
SelfMute bool
Suppress bool
}
package objects
type VoiceState struct {
GuildId string
ChannelId string
UserId string
Member Member
SessionId string
Deaf bool
Mute bool
SelfDeaf bool
SelfMute bool
Suppress bool
}

View File

@ -1,12 +1,12 @@
package utils
import "io/ioutil"
func ReadFile(path string) (string, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
return string(content), nil
}
package utils
import "io/ioutil"
func ReadFile(path string) (string, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
return string(content), nil
}

View File

@ -1,20 +1,20 @@
package utils
import (
"github.com/gin-gonic/contrib/sessions"
"strconv"
)
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
}
func GetUserId(store sessions.Session) (int64, error) {
return strconv.ParseInt(store.Get("userid").(string), 10, 64)
}
package utils
import (
"github.com/gin-gonic/contrib/sessions"
"strconv"
)
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
}
func GetUserId(store sessions.Session) (int64, error) {
return strconv.ParseInt(store.Get("userid").(string), 10, 64)
}

View File

@ -1,42 +1,42 @@
package utils
import (
"github.com/TicketsBot/GoPanel/utils/discord/objects"
"reflect"
)
func Contains(s interface{}, elem interface{}) bool {
arrV := reflect.ValueOf(s)
if arrV.Kind() == reflect.Slice {
for i := 0; i < arrV.Len(); i++ {
// XXX - panics if slice element points to an unexported struct field
// see https://golang.org/pkg/reflect/#Value.Interface
if arrV.Index(i).Interface() == elem {
return true
}
}
}
return false
}
func Insert(slice []objects.Guild, index int, value objects.Guild) []objects.Guild {
// Grow the slice by one element.
slice = slice[0 : len(slice)+1]
// Use copy to move the upper part of the slice out of the way and open a hole.
copy(slice[index+1:], slice[index:])
// Store the new value.
slice[index] = value
// Return the result.
return slice
}
func Reverse(slice []objects.Message) []objects.Message {
for i := len(slice)/2-1; i >= 0; i-- {
opp := len(slice)-1-i
slice[i], slice[opp] = slice[opp], slice[i]
}
return slice
}
package utils
import (
"github.com/TicketsBot/GoPanel/utils/discord/objects"
"reflect"
)
func Contains(s interface{}, elem interface{}) bool {
arrV := reflect.ValueOf(s)
if arrV.Kind() == reflect.Slice {
for i := 0; i < arrV.Len(); i++ {
// XXX - panics if slice element points to an unexported struct field
// see https://golang.org/pkg/reflect/#Value.Interface
if arrV.Index(i).Interface() == elem {
return true
}
}
}
return false
}
func Insert(slice []objects.Guild, index int, value objects.Guild) []objects.Guild {
// Grow the slice by one element.
slice = slice[0 : len(slice)+1]
// Use copy to move the upper part of the slice out of the way and open a hole.
copy(slice[index+1:], slice[index:])
// Store the new value.
slice[index] = value
// Return the result.
return slice
}
func Reverse(slice []objects.Message) []objects.Message {
for i := len(slice)/2-1; i >= 0; i-- {
opp := len(slice)-1-i
slice[i], slice[opp] = slice[opp], slice[i]
}
return slice
}