mirror of
https://github.com/go-shiori/shiori.git
synced 2025-09-29 08:16:40 +08:00
feat: new system info panel in webui settings (#926)
* frontend * fixed Database.DBx return value * api endpoint * updated swagger * fix openbsd variable dereference * tests * only load information if user is owner * memory improvement for other routes
This commit is contained in:
parent
650f192176
commit
72aecd2b60
18 changed files with 371 additions and 27 deletions
|
@ -181,6 +181,29 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/system/info": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get general system information like Shiori version, database, and OS",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Get general system information",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api_v1.infoResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Only owners can access this endpoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/tags": {
|
"/api/v1/tags": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -228,6 +251,31 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"api_v1.infoResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"database": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"commit": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api_v1.loginRequestPayload": {
|
"api_v1.loginRequestPayload": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -170,6 +170,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/system/info": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get general system information like Shiori version, database, and OS",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Get general system information",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api_v1.infoResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Only owners can access this endpoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/tags": {
|
"/api/v1/tags": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -217,6 +240,31 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"api_v1.infoResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"database": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"commit": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api_v1.loginRequestPayload": {
|
"api_v1.loginRequestPayload": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -1,4 +1,20 @@
|
||||||
definitions:
|
definitions:
|
||||||
|
api_v1.infoResponse:
|
||||||
|
properties:
|
||||||
|
database:
|
||||||
|
type: string
|
||||||
|
os:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
properties:
|
||||||
|
commit:
|
||||||
|
type: string
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
api_v1.loginRequestPayload:
|
api_v1.loginRequestPayload:
|
||||||
properties:
|
properties:
|
||||||
password:
|
password:
|
||||||
|
@ -239,6 +255,22 @@ paths:
|
||||||
summary: Get readable version of bookmark.
|
summary: Get readable version of bookmark.
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
|
/api/v1/system/info:
|
||||||
|
get:
|
||||||
|
description: Get general system information like Shiori version, database, and
|
||||||
|
OS
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api_v1.infoResponse'
|
||||||
|
"403":
|
||||||
|
description: Only owners can access this endpoint
|
||||||
|
summary: Get general system information
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
/api/v1/tags:
|
/api/v1/tags:
|
||||||
get:
|
get:
|
||||||
produces:
|
produces:
|
||||||
|
|
|
@ -65,7 +65,7 @@ func Connect(ctx context.Context, dbURL string) (DB, error) {
|
||||||
// DB is interface for accessing and manipulating data in database.
|
// DB is interface for accessing and manipulating data in database.
|
||||||
type DB interface {
|
type DB interface {
|
||||||
// DBx is the underlying sqlx.DB
|
// DBx is the underlying sqlx.DB
|
||||||
DBx() sqlx.DB
|
DBx() *sqlx.DB
|
||||||
|
|
||||||
// Migrate runs migrations for this database
|
// Migrate runs migrations for this database
|
||||||
Migrate(ctx context.Context) error
|
Migrate(ctx context.Context) error
|
||||||
|
@ -117,7 +117,7 @@ type DB interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type dbbase struct {
|
type dbbase struct {
|
||||||
sqlx.DB
|
*sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *dbbase) withTx(ctx context.Context, fn func(tx *sqlx.Tx) error) error {
|
func (db *dbbase) withTx(ctx context.Context, fn func(tx *sqlx.Tx) error) error {
|
||||||
|
|
|
@ -80,12 +80,12 @@ func OpenMySQLDatabase(ctx context.Context, connString string) (mysqlDB *MySQLDa
|
||||||
db.SetMaxOpenConns(100)
|
db.SetMaxOpenConns(100)
|
||||||
db.SetConnMaxLifetime(time.Second) // in case mysql client has longer timeout (driver issue #674)
|
db.SetConnMaxLifetime(time.Second) // in case mysql client has longer timeout (driver issue #674)
|
||||||
|
|
||||||
mysqlDB = &MySQLDatabase{dbbase: dbbase{*db}}
|
mysqlDB = &MySQLDatabase{dbbase: dbbase{db}}
|
||||||
return mysqlDB, err
|
return mysqlDB, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBX returns the underlying sqlx.DB object
|
// DBX returns the underlying sqlx.DB object
|
||||||
func (db *MySQLDatabase) DBx() sqlx.DB {
|
func (db *MySQLDatabase) DBx() *sqlx.DB {
|
||||||
return db.DB
|
return db.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,12 +76,12 @@ func OpenPGDatabase(ctx context.Context, connString string) (pgDB *PGDatabase, e
|
||||||
db.SetMaxOpenConns(100)
|
db.SetMaxOpenConns(100)
|
||||||
db.SetConnMaxLifetime(time.Second)
|
db.SetConnMaxLifetime(time.Second)
|
||||||
|
|
||||||
pgDB = &PGDatabase{dbbase: dbbase{*db}}
|
pgDB = &PGDatabase{dbbase: dbbase{db}}
|
||||||
return pgDB, err
|
return pgDB, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBX returns the underlying sqlx.DB object
|
// DBX returns the underlying sqlx.DB object
|
||||||
func (db *PGDatabase) DBx() sqlx.DB {
|
func (db *PGDatabase) DBx() *sqlx.DB {
|
||||||
return db.DB
|
return db.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ type tagContent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBX returns the underlying sqlx.DB object
|
// DBX returns the underlying sqlx.DB object
|
||||||
func (db *SQLiteDatabase) DBx() sqlx.DB {
|
func (db *SQLiteDatabase) DBx() *sqlx.DB {
|
||||||
return db.DB
|
return db.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,6 @@ func OpenSQLiteDatabase(ctx context.Context, databasePath string) (sqliteDB *SQL
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sqliteDB = &SQLiteDatabase{dbbase: dbbase{*db}}
|
sqliteDB = &SQLiteDatabase{dbbase: dbbase{db}}
|
||||||
return sqliteDB, nil
|
return sqliteDB, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,6 @@ func OpenSQLiteDatabase(ctx context.Context, databasePath string) (sqliteDB *SQL
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sqliteDB = &SQLiteDatabase{dbbase: dbbase{*db}}
|
sqliteDB = &SQLiteDatabase{dbbase: dbbase{db}}
|
||||||
return sqliteDB, nil
|
return sqliteDB, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ func (r *APIRoutes) Setup(g *gin.RouterGroup) model.Routes {
|
||||||
r.handle(g, "/auth", NewAuthAPIRoutes(r.logger, r.deps, r.loginHandler))
|
r.handle(g, "/auth", NewAuthAPIRoutes(r.logger, r.deps, r.loginHandler))
|
||||||
r.handle(g, "/bookmarks", NewBookmarksAPIRoutes(r.logger, r.deps))
|
r.handle(g, "/bookmarks", NewBookmarksAPIRoutes(r.logger, r.deps))
|
||||||
r.handle(g, "/tags", NewTagsPIRoutes(r.logger, r.deps))
|
r.handle(g, "/tags", NewTagsPIRoutes(r.logger, r.deps))
|
||||||
|
r.handle(g, "/system", NewSystemAPIRoutes(r.logger, r.deps))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,13 +99,11 @@ func (r *AuthAPIRoutes) loginHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMessage := loginResponseMessage{
|
response.Send(c, http.StatusOK, loginResponseMessage{
|
||||||
Token: token,
|
Token: token,
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
Expiration: expiration.Unix(),
|
Expiration: expiration.Unix(),
|
||||||
}
|
})
|
||||||
|
|
||||||
response.Send(c, http.StatusOK, responseMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// refreshHandler godoc
|
// refreshHandler godoc
|
||||||
|
@ -132,11 +130,9 @@ func (r *AuthAPIRoutes) refreshHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMessage := loginResponseMessage{
|
response.Send(c, http.StatusAccepted, loginResponseMessage{
|
||||||
Token: token,
|
Token: token,
|
||||||
}
|
})
|
||||||
|
|
||||||
response.Send(c, http.StatusAccepted, responseMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// meHandler godoc
|
// meHandler godoc
|
||||||
|
|
|
@ -97,7 +97,7 @@ type readableResponseMessage struct {
|
||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @securityDefinitions.apikey ApiKeyAuth
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} readableResponseMessage
|
// @Success 200 {object} readableResponseMessage
|
||||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||||
// @Router /api/v1/bookmarks/id/readable [get]
|
// @Router /api/v1/bookmarks/id/readable [get]
|
||||||
func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
|
func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
|
||||||
|
@ -107,12 +107,11 @@ func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
responseMessage := readableResponseMessage{
|
|
||||||
|
response.Send(c, 200, readableResponseMessage{
|
||||||
Content: bookmark.Content,
|
Content: bookmark.Content,
|
||||||
Html: bookmark.HTML,
|
Html: bookmark.HTML,
|
||||||
}
|
})
|
||||||
|
|
||||||
response.Send(c, 200, responseMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateCache godoc
|
// updateCache godoc
|
||||||
|
@ -120,7 +119,7 @@ func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
|
||||||
// @Summary Update Cache and Ebook on server.
|
// @Summary Update Cache and Ebook on server.
|
||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @securityDefinitions.apikey ApiKeyAuth
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
// @Param payload body updateCachePayload true "Update Cache Payload"`
|
// @Param payload body updateCachePayload true "Update Cache Payload"`
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} model.BookmarkDTO
|
// @Success 200 {object} model.BookmarkDTO
|
||||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||||
|
|
74
internal/http/routes/api/v1/system.go
Normal file
74
internal/http/routes/api/v1/system.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package api_v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-shiori/shiori/internal/dependencies"
|
||||||
|
"github.com/go-shiori/shiori/internal/http/context"
|
||||||
|
"github.com/go-shiori/shiori/internal/http/middleware"
|
||||||
|
"github.com/go-shiori/shiori/internal/http/response"
|
||||||
|
"github.com/go-shiori/shiori/internal/model"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemAPIRoutes struct {
|
||||||
|
logger *logrus.Logger
|
||||||
|
deps *dependencies.Dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SystemAPIRoutes) Setup(g *gin.RouterGroup) model.Routes {
|
||||||
|
g.Use(middleware.AuthenticationRequired())
|
||||||
|
g.GET("/info", r.infoHandler)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type infoResponse struct {
|
||||||
|
Version struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
} `json:"version"`
|
||||||
|
Database string `json:"database"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// System info API endpoint godoc
|
||||||
|
//
|
||||||
|
// @Summary Get general system information
|
||||||
|
// @Description Get general system information like Shiori version, database, and OS
|
||||||
|
// @Tags system
|
||||||
|
// @Produce json
|
||||||
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
|
// @Success 200 {object} infoResponse
|
||||||
|
// @Failure 403 {object} nil "Only owners can access this endpoint"
|
||||||
|
// @Router /api/v1/system/info [get]
|
||||||
|
func (r *SystemAPIRoutes) infoHandler(c *gin.Context) {
|
||||||
|
ctx := context.NewContextFromGin(c)
|
||||||
|
if !ctx.GetAccount().Owner {
|
||||||
|
response.SendError(c, http.StatusForbidden, "Only owners can access this endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Send(c, 200, infoResponse{
|
||||||
|
Version: struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}{
|
||||||
|
Tag: model.BuildVersion,
|
||||||
|
Commit: model.BuildCommit,
|
||||||
|
Date: model.BuildDate,
|
||||||
|
},
|
||||||
|
Database: r.deps.Database.DBx().DriverName(),
|
||||||
|
OS: runtime.GOOS + " (" + runtime.GOARCH + ")",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemAPIRoutes(logger *logrus.Logger, deps *dependencies.Dependencies) *SystemAPIRoutes {
|
||||||
|
return &SystemAPIRoutes{
|
||||||
|
logger: logger,
|
||||||
|
deps: deps,
|
||||||
|
}
|
||||||
|
}
|
56
internal/http/routes/api/v1/system_test.go
Normal file
56
internal/http/routes/api/v1/system_test.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package api_v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-shiori/shiori/internal/testutil"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSystemRoute(t *testing.T) {
|
||||||
|
logger := logrus.New()
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
t.Run("valid response", func(t *testing.T) {
|
||||||
|
g := testutil.NewGin()
|
||||||
|
g.Use(testutil.FakeAdminLoggedInMiddlewware)
|
||||||
|
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
|
||||||
|
router := NewSystemAPIRoutes(logger, deps)
|
||||||
|
router.Setup(g.Group("/"))
|
||||||
|
w := testutil.PerformRequest(g, http.MethodGet, "/info")
|
||||||
|
response, err := testutil.NewTestResponseFromReader(w.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
response.AssertOk(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("requires authentication", func(t *testing.T) {
|
||||||
|
g := testutil.NewGin()
|
||||||
|
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
|
||||||
|
router := NewSystemAPIRoutes(logger, deps)
|
||||||
|
router.Setup(g.Group("/"))
|
||||||
|
w := testutil.PerformRequest(g, http.MethodGet, "/info")
|
||||||
|
response, err := testutil.NewTestResponseFromReader(w.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
response.AssertNotOk(t)
|
||||||
|
require.Equal(t, http.StatusUnauthorized, w.Result().StatusCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("requires admin", func(t *testing.T) {
|
||||||
|
g := testutil.NewGin()
|
||||||
|
g.Use(testutil.FakeUserLoggedInMiddlewware)
|
||||||
|
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
|
||||||
|
router := NewSystemAPIRoutes(logger, deps)
|
||||||
|
router.Setup(g.Group("/"))
|
||||||
|
w := testutil.PerformRequest(g, http.MethodGet, "/info")
|
||||||
|
response, err := testutil.NewTestResponseFromReader(w.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
response.AssertNotOk(t)
|
||||||
|
require.Equal(t, http.StatusForbidden, w.Result().StatusCode)
|
||||||
|
})
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-shiori/shiori/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewGin returns a new gin engine with test mode enabled.
|
// NewGin returns a new gin engine with test mode enabled.
|
||||||
|
@ -46,3 +47,33 @@ func PerformRequestWithRecorder(recorder *httptest.ResponseRecorder, r http.Hand
|
||||||
r.ServeHTTP(recorder, request)
|
r.ServeHTTP(recorder, request)
|
||||||
return recorder
|
return recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FakeUserLoggedInMiddlewware is a middleware that sets a fake user account to context.
|
||||||
|
// Keep in mind that this users is not saved in database so any tests that use this middleware
|
||||||
|
// should not rely on database.
|
||||||
|
func FakeUserLoggedInMiddlewware(ctx *gin.Context) {
|
||||||
|
ctx.Set("account", &model.Account{
|
||||||
|
ID: 1,
|
||||||
|
Username: "user",
|
||||||
|
Owner: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeAdminLoggedInMiddlewware is a middleware that sets a fake admin account to context.
|
||||||
|
// Keep in mind that this users is not saved in database so any tests that use this middleware
|
||||||
|
// should not rely on database.
|
||||||
|
func FakeAdminLoggedInMiddlewware(ctx *gin.Context) {
|
||||||
|
ctx.Set("account", &model.Account{
|
||||||
|
ID: 1,
|
||||||
|
Username: "admin",
|
||||||
|
Owner: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthUserMiddleware is a middleware that manually sets an user as authenticated in the context
|
||||||
|
// to be used in tests.
|
||||||
|
func AuthUserMiddleware(user *model.AccountDTO) gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
ctx.Set("account", user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -69,6 +69,14 @@ var template = `
|
||||||
<a v-if="activeAccount.owner" @click="showDialogNewAccount">Add new account</a>
|
<a v-if="activeAccount.owner" @click="showDialogNewAccount">Add new account</a>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
<details v-if="activeAccount.owner" class="setting-group" id="setting-system-info">
|
||||||
|
<summary>System info</summary>
|
||||||
|
<ul>
|
||||||
|
<li><b>Shiori version:</b> <span>{{system.version?.tag}}<span></li>
|
||||||
|
<li><b>Database engine:</b> <span>{{system.database}}</span></li>
|
||||||
|
<li><b>Operating system:</b> <span>{{system.os}}</span></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-overlay" v-if="loading"><i class="fas fa-fw fa-spin fa-spinner"></i></div>
|
<div class="loading-overlay" v-if="loading"><i class="fas fa-fw fa-spin fa-spinner"></i></div>
|
||||||
<custom-dialog v-bind="dialog"/>
|
<custom-dialog v-bind="dialog"/>
|
||||||
|
@ -87,6 +95,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
|
system: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -160,6 +169,28 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadSystemInfo() {
|
||||||
|
if (this.system.version !== undefined) return;
|
||||||
|
|
||||||
|
fetch(new URL("api/v1/system/info", document.baseURI), {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + localStorage.getItem("shiori-token"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) throw response;
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
this.system = json.message;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.getErrorMessage(err).then((msg) => {
|
||||||
|
this.showErrorDialog(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
showDialogNewAccount() {
|
showDialogNewAccount() {
|
||||||
this.showDialog({
|
this.showDialog({
|
||||||
title: "New Account",
|
title: "New Account",
|
||||||
|
@ -369,6 +400,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadAccounts();
|
if (this.activeAccount.owner) {
|
||||||
|
this.loadAccounts();
|
||||||
|
this.loadSystemInfo();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -679,8 +679,20 @@ a {
|
||||||
&[open] summary {
|
&[open] summary {
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
|
||||||
::after {
|
&::after {
|
||||||
content: "-";
|
content: "-" !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: var(--color);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -783,4 +795,17 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#setting-system-info {
|
||||||
|
ul {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
span {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue