mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-23 04:52:40 +00:00
Add 2 new admin actions (#191)
* Synchronize all gists previews * Reset Git server hooks for all repositories
This commit is contained in:
parent
97707f7cca
commit
f52310a841
10 changed files with 213 additions and 86 deletions
156
internal/actions/actions.go
Normal file
156
internal/actions/actions.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionStatus struct {
|
||||||
|
Running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SyncReposFromFS = iota
|
||||||
|
SyncReposFromDB = iota
|
||||||
|
GitGcRepos = iota
|
||||||
|
SyncGistPreviews = iota
|
||||||
|
ResetHooks = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.Mutex
|
||||||
|
actions = make(map[int]ActionStatus)
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateActionStatus(actionType int, running bool) {
|
||||||
|
actions[actionType] = ActionStatus{
|
||||||
|
Running: running,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRunning(actionType int) bool {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return actions[actionType].Running
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(actionType int) {
|
||||||
|
mutex.Lock()
|
||||||
|
|
||||||
|
if actions[actionType].Running {
|
||||||
|
mutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActionStatus(actionType, true)
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
mutex.Lock()
|
||||||
|
updateActionStatus(actionType, false)
|
||||||
|
mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var functionToRun func()
|
||||||
|
switch actionType {
|
||||||
|
case SyncReposFromFS:
|
||||||
|
functionToRun = syncReposFromFS
|
||||||
|
case SyncReposFromDB:
|
||||||
|
functionToRun = syncReposFromDB
|
||||||
|
case GitGcRepos:
|
||||||
|
functionToRun = gitGcRepos
|
||||||
|
case SyncGistPreviews:
|
||||||
|
functionToRun = syncGistPreviews
|
||||||
|
case ResetHooks:
|
||||||
|
functionToRun = resetHooks
|
||||||
|
default:
|
||||||
|
panic("unhandled default case")
|
||||||
|
}
|
||||||
|
|
||||||
|
functionToRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncReposFromFS() {
|
||||||
|
log.Info().Msg("Syncing repositories from filesystem...")
|
||||||
|
gists, err := db.GetAllGistsRows()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot get gists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, gist := range gists {
|
||||||
|
// if repository does not exist, delete gist from database
|
||||||
|
if _, err := os.Stat(git.RepositoryPath(gist.User.Username, gist.Uuid)); err != nil && !os.IsExist(err) {
|
||||||
|
if err2 := gist.Delete(); err2 != nil {
|
||||||
|
log.Error().Err(err2).Msgf("Cannot delete gist %d", gist.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncReposFromDB() {
|
||||||
|
log.Info().Msg("Syncing repositories from database...")
|
||||||
|
entries, err := filepath.Glob(filepath.Join(config.GetHomeDir(), "repos", "*", "*"))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot read repos directories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
path := strings.Split(e, string(os.PathSeparator))
|
||||||
|
gist, _ := db.GetGist(path[len(path)-2], path[len(path)-1])
|
||||||
|
|
||||||
|
if gist.ID == 0 {
|
||||||
|
if err := git.DeleteRepository(path[len(path)-2], path[len(path)-1]); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Cannot delete repository %s/%s", path[len(path)-2], path[len(path)-1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitGcRepos() {
|
||||||
|
log.Info().Msg("Garbage collecting all repositories...")
|
||||||
|
if err := git.GcRepos(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error garbage collecting repositories")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncGistPreviews() {
|
||||||
|
log.Info().Msg("Syncing all Gist previews...")
|
||||||
|
|
||||||
|
gists, err := db.GetAllGistsRows()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot get gists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, gist := range gists {
|
||||||
|
if err = gist.UpdatePreviewAndCount(false); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Cannot update preview and count for gist %d", gist.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetHooks() {
|
||||||
|
log.Info().Msg("Resetting Git server hooks for all repositories...")
|
||||||
|
entries, err := filepath.Glob(filepath.Join(config.GetHomeDir(), "repos", "*", "*"))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot read repos directories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
path := strings.Split(e, string(os.PathSeparator))
|
||||||
|
if err := git.CreateDotGitFiles(path[len(path)-2], path[len(path)-1]); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Cannot reset hooks for repository %s/%s", path[len(path)-2], path[len(path)-1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -226,6 +226,10 @@ func (gist *Gist) Update() error {
|
||||||
return db.Omit("forked_id").Save(&gist).Error
|
return db.Omit("forked_id").Save(&gist).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gist *Gist) UpdateNoTimestamps() error {
|
||||||
|
return db.Omit("forked_id", "updated_at").Save(&gist).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (gist *Gist) Delete() error {
|
func (gist *Gist) Delete() error {
|
||||||
err := gist.DeleteRepository()
|
err := gist.DeleteRepository()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -419,7 +423,7 @@ func (gist *Gist) RPC(service string) ([]byte, error) {
|
||||||
return git.RPC(gist.User.Username, gist.Uuid, service)
|
return git.RPC(gist.User.Username, gist.Uuid, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gist *Gist) UpdatePreviewAndCount() error {
|
func (gist *Gist) UpdatePreviewAndCount(withTimestampUpdate bool) error {
|
||||||
filesStr, err := git.GetFilesOfRepository(gist.User.Username, gist.Uuid, "HEAD")
|
filesStr, err := git.GetFilesOfRepository(gist.User.Username, gist.Uuid, "HEAD")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -445,8 +449,11 @@ func (gist *Gist) UpdatePreviewAndCount() error {
|
||||||
gist.PreviewFilename = file.Filename
|
gist.PreviewFilename = file.Filename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if withTimestampUpdate {
|
||||||
return gist.Update()
|
return gist.Update()
|
||||||
}
|
}
|
||||||
|
return gist.UpdateNoTimestamps()
|
||||||
|
}
|
||||||
|
|
||||||
func (gist *Gist) VisibilityStr() string {
|
func (gist *Gist) VisibilityStr() string {
|
||||||
switch gist.Private {
|
switch gist.Private {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func InitRepository(user string, gist string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return createDotGitFiles(repositoryPath)
|
return CreateDotGitFiles(user, gist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitRepositoryViaInit(user string, gist string, ctx echo.Context) error {
|
func InitRepositoryViaInit(user string, gist string, ctx echo.Context) error {
|
||||||
|
@ -371,7 +371,7 @@ func ForkClone(userSrc string, gistSrc string, userDst string, gistDst string) e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return createDotGitFiles(repositoryPathDst)
|
return CreateDotGitFiles(userDst, gistDst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFileContent(gistTmpId string, filename string, content string) error {
|
func SetFileContent(gistTmpId string, filename string, content string) error {
|
||||||
|
@ -525,7 +525,9 @@ func GetGitVersion() (string, error) {
|
||||||
return versionFields[2], nil
|
return versionFields[2], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDotGitFiles(repositoryPath string) error {
|
func CreateDotGitFiles(user string, gist string) error {
|
||||||
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
|
|
||||||
f1, err := os.OpenFile(filepath.Join(repositoryPath, "git-daemon-export-ok"), os.O_RDONLY|os.O_CREATE, 0644)
|
f1, err := os.OpenFile(filepath.Join(repositoryPath, "git-daemon-export-ok"), os.O_RDONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -540,7 +542,7 @@ func createDotGitFiles(repositoryPath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDotGitHookFile(repositoryPath string, hook string, content string) error {
|
func createDotGitHookFile(repositoryPath string, hook string, content string) error {
|
||||||
preReceiveDst, err := os.OpenFile(filepath.Join(repositoryPath, "hooks", hook), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0744)
|
preReceiveDst, err := os.OpenFile(filepath.Join(repositoryPath, "hooks", hook), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0744)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,9 @@ admin.stats: Stats
|
||||||
admin.actions: Actions
|
admin.actions: Actions
|
||||||
admin.actions.sync-fs: Synchronize gists from filesystem
|
admin.actions.sync-fs: Synchronize gists from filesystem
|
||||||
admin.actions.sync-db: Synchronize gists from database
|
admin.actions.sync-db: Synchronize gists from database
|
||||||
admin.actions.git-gc: Garbage collect git repositories
|
admin.actions.git-gc: Garbage collect all git repositories
|
||||||
|
admin.actions.sync-previews: Synchronize all gists previews
|
||||||
|
admin.actions.reset-hooks: Reset Git server hooks for all repositories
|
||||||
admin.id: ID
|
admin.id: ID
|
||||||
admin.user: User
|
admin.user: User
|
||||||
admin.delete: Delete
|
admin.delete: Delete
|
||||||
|
|
|
@ -94,7 +94,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error {
|
||||||
// updatedAt is updated only if serviceType is receive-pack
|
// updatedAt is updated only if serviceType is receive-pack
|
||||||
if verb == "receive-pack" {
|
if verb == "receive-pack" {
|
||||||
_ = gist.SetLastActiveNow()
|
_ = gist.SetLastActiveNow()
|
||||||
_ = gist.UpdatePreviewAndCount()
|
_ = gist.UpdatePreviewAndCount(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -2,21 +2,12 @@ package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/thomiceli/opengist/internal/actions"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
syncReposFromFS = false
|
|
||||||
syncReposFromDB = false
|
|
||||||
gitGcRepos = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func adminIndex(ctx echo.Context) error {
|
func adminIndex(ctx echo.Context) error {
|
||||||
|
@ -50,9 +41,11 @@ func adminIndex(ctx echo.Context) error {
|
||||||
}
|
}
|
||||||
setData(ctx, "countKeys", countKeys)
|
setData(ctx, "countKeys", countKeys)
|
||||||
|
|
||||||
setData(ctx, "syncReposFromFS", syncReposFromFS)
|
setData(ctx, "syncReposFromFS", actions.IsRunning(actions.SyncReposFromFS))
|
||||||
setData(ctx, "syncReposFromDB", syncReposFromDB)
|
setData(ctx, "syncReposFromDB", actions.IsRunning(actions.SyncReposFromDB))
|
||||||
setData(ctx, "gitGcRepos", gitGcRepos)
|
setData(ctx, "gitGcRepos", actions.IsRunning(actions.GitGcRepos))
|
||||||
|
setData(ctx, "syncGistPreviews", actions.IsRunning(actions.SyncGistPreviews))
|
||||||
|
setData(ctx, "resetHooks", actions.IsRunning(actions.ResetHooks))
|
||||||
return html(ctx, "admin_index.html")
|
return html(ctx, "admin_index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,78 +122,31 @@ func adminGistDelete(ctx echo.Context) error {
|
||||||
|
|
||||||
func adminSyncReposFromFS(ctx echo.Context) error {
|
func adminSyncReposFromFS(ctx echo.Context) error {
|
||||||
addFlash(ctx, "Syncing repositories from filesystem...", "success")
|
addFlash(ctx, "Syncing repositories from filesystem...", "success")
|
||||||
go func() {
|
go actions.Run(actions.SyncReposFromFS)
|
||||||
if syncReposFromFS {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
syncReposFromFS = true
|
|
||||||
|
|
||||||
gists, err := db.GetAllGistsRows()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot get gists")
|
|
||||||
syncReposFromFS = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, gist := range gists {
|
|
||||||
// if repository does not exist, delete gist from database
|
|
||||||
if _, err := os.Stat(git.RepositoryPath(gist.User.Username, gist.Uuid)); err != nil && !os.IsExist(err) {
|
|
||||||
if err2 := gist.Delete(); err2 != nil {
|
|
||||||
log.Error().Err(err2).Msg("Cannot delete gist")
|
|
||||||
syncReposFromFS = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syncReposFromFS = false
|
|
||||||
}()
|
|
||||||
return redirect(ctx, "/admin-panel")
|
return redirect(ctx, "/admin-panel")
|
||||||
}
|
}
|
||||||
|
|
||||||
func adminSyncReposFromDB(ctx echo.Context) error {
|
func adminSyncReposFromDB(ctx echo.Context) error {
|
||||||
addFlash(ctx, "Syncing repositories from database...", "success")
|
addFlash(ctx, "Syncing repositories from database...", "success")
|
||||||
go func() {
|
go actions.Run(actions.SyncReposFromDB)
|
||||||
if syncReposFromDB {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
syncReposFromDB = true
|
|
||||||
entries, err := filepath.Glob(filepath.Join(config.GetHomeDir(), "repos", "*", "*"))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot read repos directories")
|
|
||||||
syncReposFromDB = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range entries {
|
|
||||||
path := strings.Split(e, string(os.PathSeparator))
|
|
||||||
gist, _ := db.GetGist(path[len(path)-2], path[len(path)-1])
|
|
||||||
|
|
||||||
if gist.ID == 0 {
|
|
||||||
if err := git.DeleteRepository(path[len(path)-2], path[len(path)-1]); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot delete repository")
|
|
||||||
syncReposFromDB = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syncReposFromDB = false
|
|
||||||
}()
|
|
||||||
return redirect(ctx, "/admin-panel")
|
return redirect(ctx, "/admin-panel")
|
||||||
}
|
}
|
||||||
|
|
||||||
func adminGcRepos(ctx echo.Context) error {
|
func adminGcRepos(ctx echo.Context) error {
|
||||||
addFlash(ctx, "Garbage collecting repositories...", "success")
|
addFlash(ctx, "Garbage collecting repositories...", "success")
|
||||||
go func() {
|
go actions.Run(actions.GitGcRepos)
|
||||||
if gitGcRepos {
|
return redirect(ctx, "/admin-panel")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
gitGcRepos = true
|
|
||||||
if err := git.GcRepos(); err != nil {
|
func adminSyncGistPreviews(ctx echo.Context) error {
|
||||||
log.Error().Err(err).Msg("Error garbage collecting repositories")
|
addFlash(ctx, "Syncing Gist previews...", "success")
|
||||||
gitGcRepos = false
|
go actions.Run(actions.SyncGistPreviews)
|
||||||
return
|
return redirect(ctx, "/admin-panel")
|
||||||
}
|
}
|
||||||
gitGcRepos = false
|
|
||||||
}()
|
func adminResetHooks(ctx echo.Context) error {
|
||||||
|
addFlash(ctx, "Resetting Git server hooks for all repositories...", "success")
|
||||||
|
go actions.Run(actions.ResetHooks)
|
||||||
return redirect(ctx, "/admin-panel")
|
return redirect(ctx, "/admin-panel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -542,7 +542,7 @@ func toggleVisibility(ctx echo.Context) error {
|
||||||
gist := getData(ctx, "gist").(*db.Gist)
|
gist := getData(ctx, "gist").(*db.Gist)
|
||||||
|
|
||||||
gist.Private = (gist.Private + 1) % 3
|
gist.Private = (gist.Private + 1) % 3
|
||||||
if err := gist.Update(); err != nil {
|
if err := gist.UpdateNoTimestamps(); err != nil {
|
||||||
return errorRes(500, "Error updating this gist", err)
|
return errorRes(500, "Error updating this gist", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,7 +806,7 @@ func checkbox(ctx echo.Context) error {
|
||||||
return errorRes(500, "Error adding and committing files", err)
|
return errorRes(500, "Error adding and committing files", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gist.UpdatePreviewAndCount(); err != nil {
|
if err = gist.UpdatePreviewAndCount(true); err != nil {
|
||||||
return errorRes(500, "Error updating the gist", err)
|
return errorRes(500, "Error updating the gist", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,7 @@ func pack(ctx echo.Context, serviceType string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = gist.SetLastActiveNow()
|
_ = gist.SetLastActiveNow()
|
||||||
_ = gist.UpdatePreviewAndCount()
|
_ = gist.UpdatePreviewAndCount(false)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,8 @@ func NewServer(isDev bool) *Server {
|
||||||
g2.POST("/sync-fs", adminSyncReposFromFS)
|
g2.POST("/sync-fs", adminSyncReposFromFS)
|
||||||
g2.POST("/sync-db", adminSyncReposFromDB)
|
g2.POST("/sync-db", adminSyncReposFromDB)
|
||||||
g2.POST("/gc-repos", adminGcRepos)
|
g2.POST("/gc-repos", adminGcRepos)
|
||||||
|
g2.POST("/sync-previews", adminSyncGistPreviews)
|
||||||
|
g2.POST("/reset-hooks", adminResetHooks)
|
||||||
g2.GET("/configuration", adminConfig)
|
g2.GET("/configuration", adminConfig)
|
||||||
g2.PUT("/set-config", adminSetConfig)
|
g2.PUT("/set-config", adminSetConfig)
|
||||||
}
|
}
|
||||||
|
|
12
templates/pages/admin_index.html
vendored
12
templates/pages/admin_index.html
vendored
|
@ -74,6 +74,18 @@
|
||||||
{{ .locale.Tr "admin.actions.git-gc" }}
|
{{ .locale.Tr "admin.actions.git-gc" }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<form action="/admin-panel/sync-previews" method="POST">
|
||||||
|
{{ .csrfHtml }}
|
||||||
|
<button type="submit" {{ if .syncGistPreviews }}disabled="disabled"{{ end }} class="whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncGistPreviews }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||||
|
{{ .locale.Tr "admin.actions.sync-previews" }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form action="/admin-panel/reset-hooks" method="POST">
|
||||||
|
{{ .csrfHtml }}
|
||||||
|
<button type="submit" {{ if .resetHooks }}disabled="disabled"{{ end }} class="whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .resetHooks }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||||
|
{{ .locale.Tr "admin.actions.reset-hooks" }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue