chore: migrate admin general
This commit is contained in:
parent
10bbd908e6
commit
9a7e83ae5f
6
.golangci.toml
Normal file
6
.golangci.toml
Normal file
@ -0,0 +1,6 @@
|
||||
#:schema https://golangci-lint.run/jsonschema/golangci.jsonschema.json
|
||||
version = "2"
|
||||
|
||||
[[linters.exclusions.rules]]
|
||||
linters = [ "errcheck" ]
|
||||
source = "^\\s*defer\\s+"
|
@ -162,13 +162,17 @@ func (api *API) registerWebAppRoutes(router *gin.Engine) {
|
||||
// TODO
|
||||
router.GET("/login", api.appGetLogin)
|
||||
router.GET("/register", api.appGetRegister)
|
||||
|
||||
// DONE
|
||||
router.GET("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdmin)
|
||||
router.POST("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminAction)
|
||||
|
||||
// TODO - WIP
|
||||
router.GET("/admin/logs", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminLogs)
|
||||
router.GET("/admin/import", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminImport)
|
||||
router.POST("/admin/import", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminImport)
|
||||
router.GET("/admin/users", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminUsers)
|
||||
router.POST("/admin/users", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appUpdateAdminUsers)
|
||||
router.GET("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdmin)
|
||||
router.POST("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminAction)
|
||||
|
||||
// Demo mode enabled configuration
|
||||
if api.cfg.DemoMode {
|
||||
|
@ -27,6 +27,8 @@ import (
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/metadata"
|
||||
"reichard.io/antholume/utils"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages"
|
||||
)
|
||||
|
||||
type adminAction string
|
||||
@ -96,21 +98,31 @@ type importResult struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
templateVars, _ := api.getBaseTemplateVars("admin", c)
|
||||
func (api *API) appGetAdmin(c *gin.Context) {
|
||||
api.renderPage(c, &pages.AdminGeneral{})
|
||||
}
|
||||
|
||||
func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
var rAdminAction requestAdminAction
|
||||
if err := c.ShouldBind(&rAdminAction); err != nil {
|
||||
log.Error("Invalid Form Bind: ", err)
|
||||
log.Error("invalid or missing form values")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
var allNotifications []*models.Notification
|
||||
switch rAdminAction.Action {
|
||||
case adminRestore:
|
||||
api.processRestoreFile(rAdminAction, c)
|
||||
return
|
||||
case adminBackup:
|
||||
api.processBackup(c, rAdminAction.BackupTypes)
|
||||
return
|
||||
case adminMetadataMatch:
|
||||
// TODO
|
||||
// 1. Documents xref most recent metadata table?
|
||||
// 2. Select all / deselect?
|
||||
allNotifications = append(allNotifications, &models.Notification{
|
||||
Type: models.NotificationTypeError,
|
||||
Content: "Metadata match not implemented",
|
||||
})
|
||||
case adminCacheTables:
|
||||
go func() {
|
||||
err := api.db.CacheTempTables(c)
|
||||
@ -118,50 +130,14 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
log.Error("Unable to cache temp tables: ", err)
|
||||
}
|
||||
}()
|
||||
case adminRestore:
|
||||
api.processRestoreFile(rAdminAction, c)
|
||||
return
|
||||
case adminBackup:
|
||||
// Vacuum
|
||||
_, err := api.db.DB.ExecContext(c, "VACUUM;")
|
||||
if err != nil {
|
||||
log.Error("Unable to vacuum DB: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Headers
|
||||
c.Header("Content-type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"AnthoLumeBackup_%s.zip\"", time.Now().Format("20060102150405")))
|
||||
|
||||
// Stream Backup ZIP Archive
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
var directories []string
|
||||
for _, item := range rAdminAction.BackupTypes {
|
||||
switch item {
|
||||
case backupCovers:
|
||||
directories = append(directories, "covers")
|
||||
case backupDocuments:
|
||||
directories = append(directories, "documents")
|
||||
}
|
||||
}
|
||||
|
||||
err := api.createBackup(c, w, directories)
|
||||
if err != nil {
|
||||
log.Error("Backup Error: ", err)
|
||||
}
|
||||
return false
|
||||
allNotifications = append(allNotifications, &models.Notification{
|
||||
Type: models.NotificationTypeSuccess,
|
||||
Content: "Initiated table cache",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) appGetAdmin(c *gin.Context) {
|
||||
templateVars, _ := api.getBaseTemplateVars("admin", c)
|
||||
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||
api.renderPage(c, &pages.AdminGeneral{}, allNotifications...)
|
||||
}
|
||||
|
||||
func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||
@ -534,6 +510,40 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "page/admin-import-results", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) processBackup(c *gin.Context, backupTypes []backupType) {
|
||||
// Vacuum
|
||||
_, err := api.db.DB.ExecContext(c, "VACUUM;")
|
||||
if err != nil {
|
||||
log.Error("Unable to vacuum DB: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Headers
|
||||
c.Header("Content-type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"AnthoLumeBackup_%s.zip\"", time.Now().Format("20060102150405")))
|
||||
|
||||
// Stream Backup ZIP Archive
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
var directories []string
|
||||
for _, item := range backupTypes {
|
||||
switch item {
|
||||
case backupCovers:
|
||||
directories = append(directories, "covers")
|
||||
case backupDocuments:
|
||||
directories = append(directories, "documents")
|
||||
}
|
||||
}
|
||||
|
||||
err := api.createBackup(c, w, directories)
|
||||
if err != nil {
|
||||
log.Error("Backup Error: ", err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Context) {
|
||||
// Validate Type & Derive Extension on MIME
|
||||
uploadedFile, err := rAdminAction.RestoreFile.Open()
|
||||
@ -790,7 +800,7 @@ func (api *API) createBackup(ctx context.Context, w io.Writer, directories []str
|
||||
}
|
||||
}
|
||||
|
||||
ar.Close()
|
||||
_ = ar.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -12,14 +12,17 @@ func Notifications(notifications []*models.Notification) g.Node {
|
||||
return nil
|
||||
}
|
||||
return h.Div(
|
||||
h.Class("fixed flex flex-col gap-2 bottom-0 right-0 p-2 sm:p-4 text-white dark:text-black"),
|
||||
h.Class("fixed flex flex-col gap-2 bottom-0 right-0 text-white dark:text-black"),
|
||||
g.Group(sliceutils.Map(notifications, notificationNode)),
|
||||
)
|
||||
}
|
||||
|
||||
func notificationNode(n *models.Notification) g.Node {
|
||||
return h.Div(
|
||||
h.Class("bg-gray-600 dark:bg-gray-400 px-4 py-2 rounded-lg shadow-lg w-64 animate-notification"),
|
||||
h.P(g.Text(n.Content)),
|
||||
h.Class("p-2 sm:p-4 animate-notification"),
|
||||
h.Div(
|
||||
h.Class("bg-gray-600 dark:bg-gray-400 px-4 py-2 rounded-lg shadow-lg w-64"),
|
||||
g.Text(n.Content),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
153
web/pages/admin.go
Normal file
153
web/pages/admin.go
Normal file
@ -0,0 +1,153 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*AdminGeneral)(nil)
|
||||
|
||||
type AdminGeneral struct{}
|
||||
|
||||
func (p *AdminGeneral) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.AdminGeneralPage),
|
||||
h.Div(
|
||||
h.Class("w-full flex flex-col gap-4 grow"),
|
||||
backupAndRestoreSection(),
|
||||
tasksSection(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func backupAndRestoreSection() g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col gap-2 grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
h.P(
|
||||
h.Class("text-lg font-semibold mb-2"),
|
||||
g.Text("Backup & Restore"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("flex flex-col gap-4"),
|
||||
backupForm(),
|
||||
restoreForm(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func backupForm() g.Node {
|
||||
return h.Form(
|
||||
h.Class("flex justify-between"),
|
||||
h.Action("./admin"),
|
||||
h.Method("POST"),
|
||||
h.Input(
|
||||
h.Type("text"),
|
||||
h.Name("action"),
|
||||
h.Value("BACKUP"),
|
||||
h.Class("hidden"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("flex gap-8"),
|
||||
h.Div(
|
||||
h.Class("flex gap-2 items-center"),
|
||||
h.Input(
|
||||
h.Type("checkbox"),
|
||||
h.ID("backup_covers"),
|
||||
h.Name("backup_types"),
|
||||
h.Value("COVERS"),
|
||||
),
|
||||
h.Label(
|
||||
h.For("backup_covers"),
|
||||
g.Text("Covers"),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("flex gap-2 items-center"),
|
||||
h.Input(
|
||||
h.Type("checkbox"),
|
||||
h.ID("backup_documents"),
|
||||
h.Name("backup_types"),
|
||||
h.Value("DOCUMENTS"),
|
||||
),
|
||||
h.Label(
|
||||
h.For("backup_documents"),
|
||||
g.Text("Documents"),
|
||||
),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("h-10 w-40"),
|
||||
ui.FormButton(g.Text("Backup"), "", ui.ButtonConfig{Variant: ui.ButtonVariantSecondary}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func restoreForm() g.Node {
|
||||
return h.Form(
|
||||
h.Class("flex justify-between"),
|
||||
h.Action("./admin"),
|
||||
h.Method("POST"),
|
||||
g.Attr("enctype", "multipart/form-data"),
|
||||
h.Input(
|
||||
h.Type("text"),
|
||||
h.Name("action"),
|
||||
h.Value("RESTORE"),
|
||||
h.Class("hidden"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("flex items-center"),
|
||||
h.Input(
|
||||
h.Type("file"),
|
||||
h.Accept(".zip"),
|
||||
h.Name("restore_file"),
|
||||
h.Class("w-full"),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("h-10 w-40"),
|
||||
ui.FormButton(g.Text("Restore"), "", ui.ButtonConfig{Variant: ui.ButtonVariantSecondary}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func tasksSection() g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
h.P(
|
||||
h.Class("text-lg font-semibold mb-4"),
|
||||
g.Text("Tasks"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("grid grid-cols-[1fr_auto] gap-x-4 gap-y-3 items-center"),
|
||||
g.Group(taskItem("Metadata Matching", "METADATA_MATCH")),
|
||||
g.Group(taskItem("Cache Tables", "CACHE_TABLES")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func taskItem(name, action string) []g.Node {
|
||||
return []g.Node{
|
||||
h.P(
|
||||
h.Class("text-black dark:text-white"),
|
||||
g.Text(name),
|
||||
),
|
||||
h.Form(
|
||||
h.Action("./admin"),
|
||||
h.Method("POST"),
|
||||
h.Input(
|
||||
h.Type("text"),
|
||||
h.Name("action"),
|
||||
h.Value(action),
|
||||
h.Class("hidden"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("h-10 w-40"),
|
||||
ui.FormButton(g.Text("Run"), "", ui.ButtonConfig{Variant: ui.ButtonVariantSecondary}),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ func Navigation(ctx models.PageContext) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "flex items-center justify-between w-full h-16"),
|
||||
Sidebar(ctx),
|
||||
h.H1(g.Attr("class", "text-xl font-bold px-6 lg:ml-44"), g.Text(ctx.Route.Title())),
|
||||
h.H1(g.Attr("class", "text-xl font-bold whitespace-nowrap px-6 lg:ml-44"), g.Text(ctx.Route.Title())),
|
||||
Dropdown(ctx.UserInfo.Username),
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user