Modmail
This commit is contained in:
parent
64c19c4dcf
commit
6e457bc1a4
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -56,9 +57,8 @@ func GetTicket(ctx *gin.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
ch := make(chan string)
|
||||
go table.GetUsername(mentionedId, ch)
|
||||
content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", <-ch))
|
||||
user, _ := cache.Instance.GetUser(mentionedId)
|
||||
content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", user.Username))
|
||||
}
|
||||
}
|
||||
|
||||
|
81
app/http/endpoints/api/modmaillogslist.go
Normal file
81
app/http/endpoints/api/modmaillogslist.go
Normal file
@ -0,0 +1,81 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/gin-gonic/gin"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var modmailPathRegex = regexp.MustCompile(`(\d+)\/modmail\/(?:free-)?([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})`)
|
||||
|
||||
type wrappedModLog struct {
|
||||
Uuid string `json:"uuid"`
|
||||
GuildId uint64 `json:"guild_id,string"`
|
||||
UserId uint64 `json:"user_id,string"`
|
||||
}
|
||||
|
||||
func GetModmailLogs(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
page, err := strconv.Atoi(ctx.Param("page"))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// filter
|
||||
var userId uint64
|
||||
|
||||
if userIdRaw, filterByUserId := ctx.GetQuery("userid"); filterByUserId {
|
||||
userId, err = strconv.ParseUint(userIdRaw, 10, 64)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": "Invalid user ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if username, filterByUsername := ctx.GetQuery("username"); filterByUsername {
|
||||
if err := cache.Instance.QueryRow(context.Background(), `select users.user_id from users where LOWER("data"->>'Username') LIKE LOWER($1) and exists(SELECT FROM members where members.guild_id=$2);`, fmt.Sprintf("%%%s%%", username), guildId).Scan(&userId); err != nil {
|
||||
ctx.AbortWithStatusJSON(404, gin.H{
|
||||
"success": false,
|
||||
"error": "User not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shouldFilter := userId > 0
|
||||
|
||||
start := pageLimit * (page - 1)
|
||||
end := start + pageLimit - 1
|
||||
|
||||
wrapped := make([]wrappedModLog, 0)
|
||||
|
||||
var archives []table.ModMailArchive
|
||||
if shouldFilter {
|
||||
archivesCh := make(chan []table.ModMailArchive)
|
||||
go table.GetModmailArchivesByUser(userId, guildId, archivesCh)
|
||||
archives = <-archivesCh
|
||||
} else {
|
||||
archivesCh := make(chan []table.ModMailArchive)
|
||||
go table.GetModmailArchivesByGuild(guildId, archivesCh)
|
||||
archives = <-archivesCh
|
||||
}
|
||||
|
||||
for i := start; i < end && i < len(archives); i++ {
|
||||
wrapped = append(wrapped, wrappedModLog{
|
||||
Uuid: archives[i].Uuid,
|
||||
GuildId: archives[i].Guild,
|
||||
UserId: archives[i].User,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.JSON(200, wrapped)
|
||||
}
|
36
app/http/endpoints/api/user.go
Normal file
36
app/http/endpoints/api/user.go
Normal file
@ -0,0 +1,36 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func UserHandler(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
userId, err := strconv.ParseUint(ctx.Param("user"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": "Invalid user ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var username string
|
||||
if err := cache.Instance.QueryRow(context.Background(), `SELECT "data"->>'Username' FROM users WHERE users.user_id=$1 AND EXISTS(SELECT FROM members WHERE members.guild_id=$2);`, userId, guildId).Scan(&username); err != nil {
|
||||
ctx.JSON(404, gin.H{
|
||||
"success": false,
|
||||
"error": "Not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"user_id": userId,
|
||||
"guild_id": guildId,
|
||||
"username": username,
|
||||
})
|
||||
}
|
@ -67,14 +67,14 @@ func LogViewHandler(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.String(500, fmt.Sprintf("Failed to archives - please contact the developers: %s", err.Error()))
|
||||
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// format to html
|
||||
html, err := Archiver.Encode(messages, ticketId)
|
||||
html, err := Archiver.Encode(messages, fmt.Sprintf("ticket-%d", ticketId))
|
||||
if err != nil {
|
||||
ctx.String(500, fmt.Sprintf("Failed to archives - please contact the developers: %s", err.Error()))
|
||||
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
|
19
app/http/endpoints/manage/modmaillogslist.go
Normal file
19
app/http/endpoints/manage/modmaillogslist.go
Normal file
@ -0,0 +1,19 @@
|
||||
package manage
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ModmailLogsHandler(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
ctx.HTML(200, "manage/modmaillogs", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildId,
|
||||
"avatar": store.Get("avatar").(string),
|
||||
"baseUrl": config.Conf.Server.BaseUrl,
|
||||
})
|
||||
}
|
86
app/http/endpoints/manage/modmaillogsview.go
Normal file
86
app/http/endpoints/manage/modmaillogsview.go
Normal file
@ -0,0 +1,86 @@
|
||||
package manage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/archiverclient"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ModmailLogViewHandler(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if utils.IsLoggedIn(store) {
|
||||
userId := utils.GetUserId(store)
|
||||
|
||||
// Verify the guild exists
|
||||
guildId, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
|
||||
return
|
||||
}
|
||||
|
||||
// Get object for selected guild
|
||||
guild, _ := cache.Instance.GetGuild(guildId, false)
|
||||
|
||||
// get ticket UUID
|
||||
uuid := ctx.Param("uuid")
|
||||
|
||||
// get ticket object
|
||||
archiveCh := make(chan table.ModMailArchive)
|
||||
go table.GetModmailArchive(uuid, archiveCh)
|
||||
archive := <-archiveCh
|
||||
|
||||
// Verify this is a valid ticket and it is closed
|
||||
if archive.Uuid == "" {
|
||||
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify this modmail ticket was for this guild
|
||||
if archive.Guild != guildId {
|
||||
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the user has permissions to be here
|
||||
isAdmin := make(chan bool)
|
||||
go utils.IsAdmin(guild, userId, isAdmin)
|
||||
if !<-isAdmin && archive.User != userId {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve ticket messages from bucket
|
||||
messages, err := Archiver.GetModmail(guildId, uuid)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiverclient.ErrExpired) {
|
||||
ctx.String(200, "Archives expired: Purchase premium for permanent log storage") // TODO: Actual error page
|
||||
return
|
||||
}
|
||||
|
||||
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// format to html
|
||||
html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", uuid))
|
||||
if err != nil {
|
||||
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data(200, gin.MIMEHTML, html)
|
||||
} else {
|
||||
ctx.Redirect(302, fmt.Sprintf("/login?noguilds&state=viewlog.%s.%s", ctx.Param("id"), ctx.Param("ticket")))
|
||||
}
|
||||
}
|
@ -11,7 +11,11 @@ import (
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ulule/limiter/v3"
|
||||
mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
@ -34,6 +38,7 @@ func StartServer() {
|
||||
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
|
||||
|
||||
router.Use(gin.Recovery())
|
||||
router.Use(createLimiter())
|
||||
|
||||
// Register templates
|
||||
router.HTMLRender = createRenderer()
|
||||
@ -42,6 +47,7 @@ func StartServer() {
|
||||
router.GET("/callback", root.CallbackHandler)
|
||||
|
||||
router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect
|
||||
router.GET("/manage/:id/logs/modmail/view/:uuid", manage.ModmailLogViewHandler) // we check in the actual handler bc of a custom redirect
|
||||
|
||||
authorized := router.Group("/", middleware.AuthenticateCookie)
|
||||
{
|
||||
@ -54,6 +60,7 @@ func StartServer() {
|
||||
|
||||
authenticateGuild.GET("/manage/:id/settings", manage.SettingsHandler)
|
||||
authenticateGuild.GET("/manage/:id/logs", manage.LogsHandler)
|
||||
authenticateGuild.GET("/manage/:id/logs/modmail", manage.ModmailLogsHandler)
|
||||
authenticateGuild.GET("/manage/:id/blacklist", manage.BlacklistHandler)
|
||||
authenticateGuild.GET("/manage/:id/panels", manage.PanelHandler)
|
||||
|
||||
@ -69,6 +76,7 @@ func StartServer() {
|
||||
{
|
||||
guildAuthApi.GET("/:id/channels", api.ChannelsHandler)
|
||||
guildAuthApi.GET("/:id/premium", api.PremiumHandler)
|
||||
guildAuthApi.GET("/:id/user/:user", api.UserHandler)
|
||||
|
||||
guildAuthApi.GET("/:id/settings", api.GetSettingsHandler)
|
||||
guildAuthApi.POST("/:id/settings", api.UpdateSettingsHandler)
|
||||
@ -82,6 +90,7 @@ func StartServer() {
|
||||
guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel)
|
||||
|
||||
guildAuthApi.GET("/:id/logs/:page", api.GetLogs)
|
||||
guildAuthApi.GET("/:id/modmail/logs/:page", api.GetModmailLogs)
|
||||
|
||||
guildAuthApi.GET("/:id/tickets", api.GetTickets)
|
||||
guildAuthApi.GET("/:id/tickets/:uuid", api.GetTicket)
|
||||
@ -106,6 +115,7 @@ func createRenderer() multitemplate.Renderer {
|
||||
|
||||
r = addManageTemplate(r, "blacklist")
|
||||
r = addManageTemplate(r, "logs")
|
||||
r = addManageTemplate(r, "modmaillogs")
|
||||
r = addManageTemplate(r, "settings")
|
||||
r = addManageTemplate(r, "ticketlist")
|
||||
r = addManageTemplate(r, "ticketview")
|
||||
@ -135,3 +145,12 @@ func addManageTemplate(renderer multitemplate.Renderer, name string) multitempla
|
||||
return renderer
|
||||
}
|
||||
|
||||
func createLimiter() func(*gin.Context) {
|
||||
store := memory.NewStore()
|
||||
rate := limiter.Rate{
|
||||
Period: time.Minute * 10,
|
||||
Limit: 600,
|
||||
}
|
||||
|
||||
return mgin.NewMiddleware(limiter.New(store, rate))
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func main() {
|
||||
database.ConnectToDatabase()
|
||||
cache.Instance = cache.NewCache()
|
||||
|
||||
manage.Archiver = archiverclient.NewArchiverClient(config.Conf.Bot.ObjectStore)
|
||||
manage.Archiver = archiverclient.NewArchiverClientWithTimeout(config.Conf.Bot.ObjectStore, time.Second * 15)
|
||||
|
||||
utils.LoadEmoji()
|
||||
|
||||
|
39
database/table/modmailarchive.go
Normal file
39
database/table/modmailarchive.go
Normal file
@ -0,0 +1,39 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ModMailArchive struct {
|
||||
Uuid string `gorm:"column:UUID;type:varchar(36);unique;primary_key"`
|
||||
Guild uint64 `gorm:"column:GUILDID"`
|
||||
User uint64 `gorm:"column:USERID"`
|
||||
CloseTime time.Time `gorm:"column:CLOSETIME"`
|
||||
}
|
||||
|
||||
func (ModMailArchive) TableName() string {
|
||||
return "modmail_archive"
|
||||
}
|
||||
|
||||
func (m *ModMailArchive) Store() {
|
||||
database.Database.Create(m)
|
||||
}
|
||||
|
||||
func GetModmailArchive(uuid string, ch chan ModMailArchive) {
|
||||
var row ModMailArchive
|
||||
database.Database.Where(ModMailArchive{Uuid: uuid}).Take(&row)
|
||||
ch <- row
|
||||
}
|
||||
|
||||
func GetModmailArchivesByUser(userId, guildId uint64, ch chan []ModMailArchive) {
|
||||
var rows []ModMailArchive
|
||||
database.Database.Where(ModMailArchive{User: userId, Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
|
||||
ch <- rows
|
||||
}
|
||||
|
||||
func GetModmailArchivesByGuild(guildId uint64, ch chan []ModMailArchive) {
|
||||
var rows []ModMailArchive
|
||||
database.Database.Where(ModMailArchive{Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
|
||||
ch <- rows
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
)
|
||||
|
||||
type UsernameNode struct {
|
||||
Id uint64 `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 uint64, ch chan string) {
|
||||
node := UsernameNode{Name: "Unknown"}
|
||||
database.Database.Where(&UsernameNode{Id: id}).First(&node)
|
||||
ch <- node.Name
|
||||
}
|
||||
|
||||
func GetUserNodes(ids []uint64) []UsernameNode {
|
||||
var nodes []UsernameNode
|
||||
database.Database.Where(ids).Find(&nodes)
|
||||
return nodes
|
||||
}
|
||||
|
||||
func GetUserId(name, discrim string) uint64 {
|
||||
var node UsernameNode
|
||||
database.Database.Where(&UsernameNode{Name: name, Discriminator: discrim}).First(&node)
|
||||
return node.Id
|
||||
}
|
5
go.mod
5
go.mod
@ -4,7 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/TicketsBot/archiverclient v0.0.0-20200420161043-3532ff9ea943
|
||||
github.com/TicketsBot/archiverclient v0.0.0-20200425115930-0ca198cc8306
|
||||
github.com/apex/log v1.1.2
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
@ -21,6 +21,7 @@ require (
|
||||
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62
|
||||
github.com/rxdn/gdl v0.0.0-20200417164852-76b2d3c847c1
|
||||
github.com/rxdn/gdl v0.0.0-20200421193445-f200b9f466d7
|
||||
github.com/ulule/limiter/v3 v3.5.0
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
)
|
||||
|
@ -7,6 +7,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/logs"><i class="fas fa-copy icon"></i>Logs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/logs/modmail"><i class="fas fa-copy icon"></i>Modmail Logs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/blacklist"><i class="fas fa-ban icon"></i>Blacklist</a>
|
||||
</li>
|
||||
|
@ -65,14 +65,6 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log-container">
|
||||
{{range .logs}}
|
||||
<tr>
|
||||
<td>{{.ticketid}}</td>
|
||||
<td>{{.username}}</td>
|
||||
<td>{{.userid}}</td>
|
||||
<td><a href="/manage/{{$.guildId}}/logs/view/{{.ticketid}}">View</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -80,7 +72,7 @@
|
||||
<div class="col-md-12">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
|
||||
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page <span id="page-number">1</span></p>
|
||||
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -110,10 +102,6 @@
|
||||
appendTd(tr, log.userid);
|
||||
appendButton(tr, 'View', () => { location.href = '/manage/{{.guildId}}/logs/view/' + log.ticketid });
|
||||
|
||||
// create view button
|
||||
const viewTd = document.createElement('td');
|
||||
tr.appendChild(viewTd);
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
@ -135,6 +123,8 @@
|
||||
for (log of res.data) {
|
||||
appendLog(log);
|
||||
}
|
||||
|
||||
document.getElementById('page-number').innerText = page;
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
|
166
public/templates/views/modmaillogs.tmpl
Normal file
166
public/templates/views/modmaillogs.tmpl
Normal file
@ -0,0 +1,166 @@
|
||||
{{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 white" 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 onsubmit="filterLogs(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-4 px-1">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input name="username" type="text" class="form-control" placeholder="Username" id="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" id="userid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary mx-auto btn-fill"><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>Username</th>
|
||||
<th>User ID</th>
|
||||
<th>Archive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log-container">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page <span id="page-number">1</span></p>
|
||||
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative">
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
|
||||
async function getUsername(userId) {
|
||||
const res = await axios.get('/api/{{.guildId}}/user/' + userId);
|
||||
return res.data.username;
|
||||
}
|
||||
|
||||
async function appendLog(log) {
|
||||
const username = await getUsername(log.user_id);
|
||||
|
||||
const container = document.getElementById('log-container');
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
appendTd(tr, username);
|
||||
appendTd(tr, log.user_id);
|
||||
appendButton(tr, 'View', () => { location.href = '/manage/{{.guildId}}/logs/modmail/view/' + log.uuid });
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function loadPage(page, ticketId, username, userId) {
|
||||
const container = document.getElementById('log-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
let url = '/api/{{.guildId}}/modmail/logs/' + page;
|
||||
|
||||
if (username !== undefined) {
|
||||
url += `?username=${username}`;
|
||||
} else if (userId !== undefined) {
|
||||
url += `?userid=${userId}`;
|
||||
}
|
||||
|
||||
const res = await axios.get(url);
|
||||
if (res.status === 200) {
|
||||
for (log of res.data) {
|
||||
await appendLog(log);
|
||||
}
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
|
||||
document.getElementById('page-number').innerText = page;
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function next() {
|
||||
currentPage += 1;
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
async function previous() {
|
||||
if (currentPage <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
currentPage -= 1;
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
async function filterLogs() {
|
||||
const username = document.getElementById('username').value;
|
||||
const userId = document.getElementById('userid').value;
|
||||
|
||||
if (username !== "") {
|
||||
await loadPage(1, undefined, username);
|
||||
} else if (userId !== "") {
|
||||
await loadPage(1, undefined, undefined, userId);
|
||||
} else {
|
||||
await loadPage(1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -111,7 +111,12 @@
|
||||
}
|
||||
|
||||
function getChannelName(channels, channelId) {
|
||||
return channels.find(ch => ch.id === channelId);
|
||||
const ch = channels.find(ch => ch.id === channelId);
|
||||
if (ch === undefined) {
|
||||
return "Unknown";
|
||||
} else {
|
||||
return ch.name;
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePanel(messageId) {
|
||||
@ -200,10 +205,10 @@
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = panel.message_id; // TODO: When we call this after creating a panel, we don't know the message ID yet
|
||||
|
||||
appendTd(tr, `#${getChannelName(channels, panel.channel_id).name}`);
|
||||
appendTd(tr, `#${getChannelName(channels, panel.channel_id)}`);
|
||||
appendTd(tr, panel.title);
|
||||
appendTd(tr, panel.content);
|
||||
appendTd(tr, getChannelName(channels, panel.category_id).name);
|
||||
appendTd(tr, getChannelName(channels, panel.category_id));
|
||||
|
||||
// build remove button
|
||||
const deleteTd = document.createElement('td');
|
||||
|
Loading…
x
Reference in New Issue
Block a user