use Go templating

This commit is contained in:
Dot-Rar 2020-02-08 17:10:56 +00:00
parent 5472f20964
commit 7aba0cc253
28 changed files with 756 additions and 915 deletions

View File

@ -1,7 +1,6 @@
package manage package manage
import ( import (
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -95,7 +94,7 @@ func BlacklistHandler(ctx *gin.Context) {
} }
} }
utils.Respond(ctx, template.TemplateBlacklist.Render(map[string]interface{}{ ctx.HTML(200, "manage/blacklist", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildIdStr,
"csrf": store.Get("csrf").(string), "csrf": store.Get("csrf").(string),
@ -104,6 +103,6 @@ func BlacklistHandler(ctx *gin.Context) {
"blacklisted": blacklisted, "blacklisted": blacklisted,
"userNotFound": userNotFound, "userNotFound": userNotFound,
"isStaff": isStaff, "isStaff": isStaff,
})) })
} }
} }

View File

@ -1,7 +1,6 @@
package manage package manage
import ( import (
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -95,7 +94,7 @@ func LogsHandler(ctx *gin.Context) {
}) })
} }
utils.Respond(ctx, template.TemplateLogs.Render(map[string]interface{}{ ctx.HTML(200, "manage/logs",gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildIdStr,
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
@ -105,7 +104,7 @@ func LogsHandler(ctx *gin.Context) {
"nextPage": page + 1, "nextPage": page + 1,
"logs": formattedLogs, "logs": formattedLogs,
"page": page, "page": page,
})) })
} else { } else {
ctx.Redirect(302, "/login") ctx.Redirect(302, "/login")
} }

View File

@ -3,7 +3,6 @@ package manage
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -254,7 +253,7 @@ func SettingsHandler(ctx *gin.Context) {
table.SetUserCanClose(guildId, usersCanClose) table.SetUserCanClose(guildId, usersCanClose)
} }
utils.Respond(ctx, template.TemplateSettings.Render(map[string]interface{}{ ctx.HTML(200, "manage/settings", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildIdStr,
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
@ -272,7 +271,7 @@ func SettingsHandler(ctx *gin.Context) {
"panelcontent": panelContent, "panelcontent": panelContent,
"panelcolour": strconv.FormatInt(int64(panelColour), 16), "panelcolour": strconv.FormatInt(int64(panelColour), 16),
"usersCanClose": usersCanClose, "usersCanClose": usersCanClose,
})) })
} else { } else {
ctx.Redirect(302, "/login") ctx.Redirect(302, "/login")
} }

View File

@ -1,7 +1,6 @@
package manage package manage
import ( import (
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -98,13 +97,13 @@ func TicketListHandler(ctx *gin.Context) {
}) })
} }
utils.Respond(ctx, template.TemplateTicketList.Render(map[string]interface{}{ ctx.HTML(200, "manage/ticketlist", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildIdStr,
"csrf": store.Get("csrf").(string), "csrf": store.Get("csrf").(string),
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
"baseUrl": config.Conf.Server.BaseUrl, "baseUrl": config.Conf.Server.BaseUrl,
"tickets": ticketsFormatted, "tickets": ticketsFormatted,
})) })
} }
} }

View File

@ -2,7 +2,6 @@ package manage
import ( import (
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -108,7 +107,7 @@ func TicketViewHandler(ctx *gin.Context) {
premium := make(chan bool) premium := make(chan bool)
go utils.IsPremiumGuild(store, guildIdStr, premium) go utils.IsPremiumGuild(store, guildIdStr, premium)
utils.Respond(ctx, template.TemplateTicketView.Render(map[string]interface{}{ ctx.HTML(200, "manage/ticketview", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildIdStr,
"csrf": store.Get("csrf").(string), "csrf": store.Get("csrf").(string),
@ -120,6 +119,6 @@ func TicketViewHandler(ctx *gin.Context) {
"ticketId": ticket.TicketId, "ticketId": ticket.TicketId,
"include_mock": true, "include_mock": true,
"premium": <-premium, "premium": <-premium,
})) })
} }
} }

View File

@ -1,7 +1,6 @@
package root package root
import ( import (
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -40,24 +39,14 @@ func IndexHandler(ctx *gin.Context) {
} }
} }
var servers []map[string]string ctx.HTML(200, "main/index", gin.H{
for _, server := range adminGuilds {
element := map[string]string{
"serverid": server.Id,
"servername": server.Name,
}
servers = append(servers, element)
}
utils.Respond(ctx, template.TemplateIndex.Render(map[string]interface{}{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"baseurl": config.Conf.Server.BaseUrl, "baseurl": config.Conf.Server.BaseUrl,
"servers": servers, "servers": adminGuilds,
"empty": len(servers) == 0, "empty": len(adminGuilds) == 0,
"isIndex": true, "isIndex": true,
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
})) })
} else { } else {
ctx.Redirect(302, "/login") ctx.Redirect(302, "/login")
} }

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/app/http/endpoints/manage" "github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
"github.com/TicketsBot/GoPanel/app/http/endpoints/root" "github.com/TicketsBot/GoPanel/app/http/endpoints/root"
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/gin-contrib/multitemplate"
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -15,10 +15,6 @@ import (
func StartServer() { func StartServer() {
log.Println("Starting HTTP server") log.Println("Starting HTTP server")
// Compile templates
template.LoadLayouts()
template.LoadTemplates()
router := gin.Default() router := gin.Default()
// Sessions // Sessions
@ -35,45 +31,65 @@ func StartServer() {
// Handle static asset requests // Handle static asset requests
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false))) router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
// Root // Register templates
router.HTMLRender = createRenderer()
router.GET("/", root.IndexHandler) router.GET("/", root.IndexHandler)
// /login
router.GET("/login", root.LoginHandler) router.GET("/login", root.LoginHandler)
// /callback
router.GET("/callback", root.CallbackHandler) router.GET("/callback", root.CallbackHandler)
// /logout
router.GET("/logout", root.LogoutHandler) router.GET("/logout", root.LogoutHandler)
// /manage/:id/settings
router.GET("/manage/:id/settings", manage.SettingsHandler) router.GET("/manage/:id/settings", manage.SettingsHandler)
// /manage/:id/logs/page/:page
router.GET("/manage/:id/logs/page/:page", manage.LogsHandler) router.GET("/manage/:id/logs/page/:page", manage.LogsHandler)
// /manage/:id/logs/view/:uuid
router.GET("/manage/:id/logs/view/:uuid", manage.LogViewHandler) router.GET("/manage/:id/logs/view/:uuid", manage.LogViewHandler)
// /manage/:id/blacklist
router.GET("/manage/:id/blacklist", manage.BlacklistHandler) router.GET("/manage/:id/blacklist", manage.BlacklistHandler)
// /manage/:id/blacklist/remove/:user
router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler) router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler)
// /manage/:id/tickets
router.GET("/manage/:id/tickets", manage.TicketListHandler) router.GET("/manage/:id/tickets", manage.TicketListHandler)
// /manage/:id/tickets/view/:uuid
router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler) router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler)
// POST /manage/:id/tickets/view/:uuid
router.POST("/manage/:id/tickets/view/:uuid", manage.SendMessage) router.POST("/manage/:id/tickets/view/:uuid", manage.SendMessage)
router.GET("/webchat", manage.WebChatWs) router.GET("/webchat", manage.WebChatWs)
if err := router.Run(config.Conf.Server.Host); err != nil { if err := router.Run(config.Conf.Server.Host); err != nil {
panic(err) panic(err)
} }
} }
func createRenderer() multitemplate.Renderer {
r := multitemplate.NewRenderer()
r = addMainTemplate(r, "index")
r = addManageTemplate(r, "blacklist")
r = addManageTemplate(r, "logs")
r = addManageTemplate(r, "settings")
r = addManageTemplate(r, "ticketlist")
r = addManageTemplate(r, "ticketview")
return r
}
func addMainTemplate(renderer multitemplate.Renderer, name string) multitemplate.Renderer {
renderer.AddFromFiles(fmt.Sprintf("main/%s", name),
"./public/templates/layouts/main.tmpl",
"./public/templates/includes/head.tmpl",
"./public/templates/includes/sidebar.tmpl",
fmt.Sprintf("./public/templates/views/%s.tmpl", name),
)
return renderer
}
func addManageTemplate(renderer multitemplate.Renderer, name string) multitemplate.Renderer {
renderer.AddFromFiles(fmt.Sprintf("manage/%s", name),
"./public/templates/layouts/manage.tmpl",
"./public/templates/includes/head.tmpl",
"./public/templates/includes/sidebar.tmpl",
"./public/templates/includes/navbar.tmpl",
fmt.Sprintf("./public/templates/views/%s.tmpl", name),
)
return renderer
}

View File

@ -1,85 +0,0 @@
package template
import (
"fmt"
"github.com/hoisie/mustache"
)
type Layout struct {
compiled *mustache.Template
}
type Template struct {
compiled *mustache.Template
Layout Layout
}
var (
LayoutMain Layout
LayoutManage Layout
TemplateIndex Template
TemplateLogs Template
TemplateSettings Template
TemplateBlacklist Template
TemplateTicketList Template
TemplateTicketView Template
)
func (t *Template) Render(context ...interface{}) string {
return t.compiled.RenderInLayout(t.Layout.compiled, context[0])
}
func LoadLayouts() {
LayoutMain = Layout{
compiled: loadLayout("main"),
}
LayoutManage = Layout{
compiled: loadLayout("manage"),
}
}
func LoadTemplates() {
TemplateIndex = Template{
compiled: loadTemplate("index"),
Layout: LayoutMain,
}
TemplateLogs = Template{
compiled: loadTemplate("logs"),
Layout: LayoutManage,
}
TemplateSettings = Template{
compiled: loadTemplate("settings"),
Layout: LayoutManage,
}
TemplateBlacklist = Template{
compiled: loadTemplate("blacklist"),
Layout: LayoutManage,
}
TemplateTicketList = Template{
compiled: loadTemplate("ticketlist"),
Layout: LayoutManage,
}
TemplateTicketView = Template{
compiled: loadTemplate("ticketview"),
Layout: LayoutManage,
}
}
func loadLayout(name string) *mustache.Template {
tmpl, err := mustache.ParseFile(fmt.Sprintf("./public/templates/layouts/%s.mustache", name))
if err != nil {
panic(err)
}
return tmpl
}
func loadTemplate(name string) *mustache.Template {
tmpl, err := mustache.ParseFile(fmt.Sprintf("./public/templates/views/%s.mustache", name))
if err != nil {
panic(err)
}
return tmpl
}

View File

@ -0,0 +1,38 @@
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%;
}
.avatar {
background: url("{{.avatar}}?size=32");
width: 28px;
height: 29px;
display: block;
background-size: cover;
border-radius: 50%;
}
.nav-link {
color: white !important;
}
.filterCard {
cursor: pointer;
}
.table td, .table th {
text-align: center;
}

View File

@ -0,0 +1,29 @@
{{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">
<!-- 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">
{{end}}

View File

@ -0,0 +1,20 @@
{{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>
</ul>
</div>
</nav>
{{end}}

View File

@ -0,0 +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>
{{end}}

View File

@ -1,94 +0,0 @@
<!DOCTYPE html>
<html>
<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">
<style>
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%;
}
.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>
<!-- Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
</head>
<body>
<div class="wrapper">
<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>
<div class="main-panel">
{{{content}}}
</div>
</div>
</body>
</html>

View File

@ -0,0 +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>

View File

@ -1,131 +0,0 @@
<!DOCTYPE html>
<html>
<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">
<style>
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%;
}
.avatar {
background: url("{{avatar}}?size=32");
width: 28px;
height: 29px;
display: block;
background-size: cover;
border-radius: 50%;
}
.nav-link {
color: white !important;
}
.filterCard {
cursor: pointer;
}
.table td, .table th {
text-align: center;
}
</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>
{{#include_mock}}
<!-- Discord theme -->
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
{{/include_mock}}
<!-- Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
</head>
<body>
<div class="wrapper">
<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>
<div class="main-panel">
<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>
</ul>
</div>
</nav>
{{{content}}}
</div>
</div>
</body>
</html>

View File

@ -0,0 +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>

View File

@ -1,113 +0,0 @@
<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>
{{#blacklisted}}
<tr>
<td>{{userId}}</td>
<td>{{username}}#{{discrim}}</td>
<td><a href="{{baseUrl}}/manage/{{guildId}}/blacklist/remove/{{userId}}?c={{csrf}}">Remove</a></td>
</tr>
{{/blacklisted}}
</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">
{{#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>
{{/userNotFound}}
{{#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>
{{/isStaff}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>

View File

@ -0,0 +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>
{{end}}

View File

@ -1,45 +0,0 @@
<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>
{{^empty}}
<p class="card-category">Select a server to manage below</p>
{{/empty}}
</div>
<div class="card-body">
{{#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>
{{/empty}}
{{^empty}}
<div class="card-body table-responsive">
<table class="table table-hover table-striped">
<thead>
<th>Server Name</th>
</thead>
<tbody>
{{#servers}}
<tr>
<td>
<a href="/manage/{{serverid}}/settings">
{{servername}}
</a>
</td>
</tr>
{{/servers}}
</tbody>
</table>
</div>
{{/empty}}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +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>
{{end}}

View File

@ -1,100 +0,0 @@
<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>
{{#logs}}
<tr>
<td>{{ticketid}}</td>
<td>{{username}}</td>
<td>{{userid}}</td>
<td><a href="{{baseUrl}}/manage/{{guildId}}/logs/view/{{uuid}}">{{uuid}}</a></td>
</tr>
{{/logs}}
</tbody>
</table>
<div class="row">
<div class="col-md-12">
<ul class="pagination justify-content-center">
{{#isPageOne}}
<li class="disabled"><a href="#"><i class="fas fa-chevron-left"></i></a></li>
{{/isPageOne}}
{{^isPageOne}}
<li class="waves-effect"><a href="/manage/{{guildId}}/logs/page/{{previousPage}}"><i class="fas fa-chevron-left"></i></a></li>
{{/isPageOne}}
<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>

View File

@ -0,0 +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>
{{end}}

View File

@ -1,165 +0,0 @@
<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>
{{^empty}}
<p class="card-category">Select a server to manage below</p>
{{/empty}}
</div>
<div class="card-body">
<form>
<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" {{#pingEveryone}}checked{{/pingEveryone}} 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">
{{#channels}}
<option {{#active}}selected{{/active}} value="{{channelid}}">{{channelname}}</option>
{{/channels}}
</select>
</div>
</div>
<div class="col-md-5 px-1">
<div class="form-group">
<label>Channel Category</label>
<select class="form-control" name="category">
{{#categories}}
<option {{#active}}selected{{/active}} value="{{categoryid}}">{{categoryname}}</option>
{{/categories}}
</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" {{#usersCanClose}}checked{{/usersCanClose}} style="width:30px;height:30px;">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 pr-1">
<label>Panel Title</label>
<input name="paneltitle" type="text" class="form-control" placeholder="Open A Ticket" value="{{paneltitle}}">
</div>
<div class="col-md-6 pr-1">
<label>Panel Content</label>
<input name="panelcontent" type="text" class="form-control" placeholder="React with :envelope_with_arrow: to open a ticket" value="{{panelcontent}}">
</div>
<div class="col-md-3 pr-1">
<label>Panel Colour (Hex)</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<input name="panelcolour" type="text" class="form-control" placeholder="23A31A" value="{{panelcolour}}">
</div>
</div>
</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">
{{#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>
{{/invalidPrefix}}
{{#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>
{{/invalidWelcomeMessage}}
{{#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 ticketLimit you specified was invalid
</div>
</div>
{{/invalidTicketLimit}}
</div>
</div>
<script>
$('.toast').toast('show');
$('#pingeveryone').prop('indeterminate', {{pingEveryone}});
</script>
</div>

View File

@ -0,0 +1,167 @@
{{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>
<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 .active}}selected{{end}} value="{{.channelid}}">{{.channelname}}</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 .active}}selected{{end}} value="{{.categoryid}}">{{.categoryname}}</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>
<div class="row">
<div class="col-md-3 pr-1">
<label>Panel Title</label>
<input name="paneltitle" type="text" class="form-control" placeholder="Open A Ticket" value="{{.paneltitle}}">
</div>
<div class="col-md-6 pr-1">
<label>Panel Content</label>
<input name="panelcontent" type="text" class="form-control" placeholder="React with :envelope_with_arrow: to open a ticket" value="{{.panelcontent}}">
</div>
<div class="col-md-3 pr-1">
<label>Panel Colour (Hex)</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">#</div>
</div>
<input name="panelcolour" type="text" class="form-control" placeholder="23A31A" value="{{.panelcolour}}">
</div>
</div>
</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 ticketLimit you specified was invalid
</div>
</div>
{{end}}
</div>
</div>
<script>
$('.toast').toast('show');
$('#pingeveryone').prop('indeterminate', {{.pingEveryone}});
</script>
</div>
{{end}}

View File

@ -1,3 +1,4 @@
{{define "content"}}
<div class="content"> <div class="content">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
@ -10,22 +11,22 @@
<div class="card-body table-responsive"> <div class="card-body table-responsive">
<table class="table table-hover table-striped"> <table class="table table-hover table-striped">
<thead> <thead>
<tr> <tr>
<th>Ticket ID</th> <th>Ticket ID</th>
<th>User</th> <th>User</th>
<th>Additional Members</th> <th>Additional Members</th>
<th>View</th> <th>View</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{#tickets}} {{range .tickets}}
<tr> <tr>
<td>{{ticketId}}</td> <td>{{.ticketId}}</td>
<td>{{username}}#{{discrim}}</td> <td>{{.username}}#{{.discrim}}</td>
<td>{{#members}}{{username}}#{{discrim}}{{sep}}{{/members}}</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> <td><a class="btn btn-primary btn-sm" role="button" href="/manage/{{$.guildId}}/tickets/view/{{.uuid}}">View</a></td>
</tr> </tr>
{{/tickets}} {{end}}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -39,3 +40,4 @@
$('.toast').toast('show'); $('.toast').toast('show');
</script> </script>
</div> </div>
{{end}}

View File

@ -1,109 +0,0 @@
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="discord-container">
<div class="channel-header">
<span class="channel-name">#ticket-{{ticketId}}</span>
</div>
<div id="message-container">
{{#messages}}
<div class="message">
<b>{{username}}</b>
{{content}}
</div>
{{/messages}}
</div>
<div class="input-container">
<form action="javascript:sendMessage()">
{{#premium}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Message #ticket-{{ticketId}}">
{{/premium}}
{{^premium}}
<input type="text" class="form-control message-input" id="message" name="message"
placeholder="Premium users get live messages and can respond through webchat" disabled>
{{/premium}}
</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">
{{#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>
{{/isError}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>
</div>
<script>
// Scroll to bottom
let container = document.getElementById("message-container");
container.scrollTop = container.scrollHeight;
</script>
{{#premium}}
<script>
ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
ws.onopen = (evt) => {
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>
{{/premium}}

View File

@ -0,0 +1,110 @@
{{define "content"}}
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<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>
ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
ws.onopen = (evt) => {
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,7 +0,0 @@
package utils
import "github.com/gin-gonic/gin"
func Respond(ctx *gin.Context, s string) {
ctx.Data(200, "text/html; charset=utf-8", []byte(s))
}