From 994dd49f6d5109348c9d7df89b979f0f7566f37a Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 02:21:50 +0100 Subject: [PATCH 01/18] Added examples for .env --- README.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aa0ecf1..620fb36 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,34 @@ # GohCMS + ## 🚧 This project is under development ! + The features are not complete, not tested and subject to many changes. + ## Setup + ### Use with Docker + To **run** the app, run the command + ```shell docker-compose up ``` ### Build and run it yourself + TODO + ## Environment variables + - ./env - - APP_BASE_API_PATH - - APP_BASE_FRONT_PATH - - APP_API_ADDRESS - - APP_FRONT_ADDRESS - - APP_API_PORT - - APP_FRONT_PORT - - APP_JWT_SECRET + - APP_API_ADDRESS: `http://example.com` + - APP_FRONT_ADDRESS: `http://example.com` + - APP_BASE_API_PATH: `/example` + - APP_BASE_FRONT_PATH: `/example` + - APP_API_PORT: `1234` + - APP_FRONT_PORT: `1234` + - APP_JWT_SECRET: `secret` + ## Demo -TODO \ No newline at end of file + +TODO From 607dfd78a3c6b3be9be0d9750711fcff7eaa3d2a Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 16:31:25 +0100 Subject: [PATCH 02/18] Implemented edition mode and success/errors modals --- cmd/main.go | 2 +- .../src/components/ModalSuccessError.vue | 88 +++++++++++++++++++ web/admin-gui/src/utils/database.ts | 22 ++--- web/admin-gui/src/views/ArticlesEdit.vue | 46 ++++++++-- 4 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 web/admin-gui/src/components/ModalSuccessError.vue diff --git a/cmd/main.go b/cmd/main.go index bdeefec..eb38be8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -66,7 +66,7 @@ func initArticlesRoutes(r *gin.RouterGroup) { articlesRouter.GET("/", articles.GetArticleHandler) articlesRouter.GET("/:id", articles.GetArticleHandler) articlesRouter.POST("/:id", articles.AddArticleHandler) - articlesRouter.PATCH("/:id", articles.EditArticleHandler) + articlesRouter.PUT("/:id", articles.EditArticleHandler) articlesRouter.DELETE("/:id", articles.DeleteArticleHandler) } diff --git a/web/admin-gui/src/components/ModalSuccessError.vue b/web/admin-gui/src/components/ModalSuccessError.vue new file mode 100644 index 0000000..57300d2 --- /dev/null +++ b/web/admin-gui/src/components/ModalSuccessError.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/web/admin-gui/src/utils/database.ts b/web/admin-gui/src/utils/database.ts index a56c126..5a0ddaf 100644 --- a/web/admin-gui/src/utils/database.ts +++ b/web/admin-gui/src/utils/database.ts @@ -11,25 +11,27 @@ export interface Article { online: boolean } -export async function getArticles(id: string) : Promise> { +export async function getArticles(id: string): Promise> { return await fetch(`${baseApiUrl}/articles/${id}`, { credentials: 'include', method: 'GET', }) .then(result => result.json()) - .catch(error => { - console.error(error) - }) } -export async function postArticle(article: Article) : Promise { +async function sendArticle(article: Article, method: 'POST' | 'PUT') { return await fetch(`${baseApiUrl}/articles/${article.titleID}`, { credentials: 'include', - method: 'POST', - body: JSON.stringify(article) + method, + body: JSON.stringify(article) }) .then(result => result.json()) - .catch(error => { - console.error(error) - }) +} + +export async function postArticle(article: Article): Promise { + return await sendArticle(article, 'POST') +} + +export async function editArticle(article: Article): Promise { + return await sendArticle(article, 'PUT') } \ No newline at end of file diff --git a/web/admin-gui/src/views/ArticlesEdit.vue b/web/admin-gui/src/views/ArticlesEdit.vue index cec1eb4..1705836 100644 --- a/web/admin-gui/src/views/ArticlesEdit.vue +++ b/web/admin-gui/src/views/ArticlesEdit.vue @@ -1,23 +1,48 @@ @@ -33,9 +58,14 @@ function saveContent() { + + + + \ No newline at end of file From 0461bb08ef185005fec41aff82c543f86317aabb Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 20:38:00 +0100 Subject: [PATCH 04/18] Fixed typo --- web/admin-gui/src/views/Home.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/admin-gui/src/views/Home.vue b/web/admin-gui/src/views/Home.vue index 9742040..ee22d88 100644 --- a/web/admin-gui/src/views/Home.vue +++ b/web/admin-gui/src/views/Home.vue @@ -4,7 +4,7 @@ From 245ddbc137ab7aa2b54cc1e2beab3ba2fb545a21 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 20:55:51 +0100 Subject: [PATCH 05/18] Database related operation files reorganization --- internal/database/database.go | 77 ----------------------------------- internal/database/delete.go | 19 +++++++++ internal/database/edit.go | 29 +++++++++++++ internal/database/get.go | 35 ++++++++++++++++ internal/database/push.go | 15 +++++++ 5 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 internal/database/delete.go create mode 100644 internal/database/edit.go create mode 100644 internal/database/get.go create mode 100644 internal/database/push.go diff --git a/internal/database/database.go b/internal/database/database.go index 0ad9853..1602d82 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -2,9 +2,7 @@ package database import ( "context" - "errors" - "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -14,11 +12,6 @@ type Location struct { Collection string } -type DocumentUpdate struct { - Filter gin.H `json:"filter"` - Update gin.H `json:"update"` -} - func GetNewClient() *mongo.Client { client, err := mongo.Connect( context.TODO(), @@ -28,73 +21,3 @@ func GetNewClient() *mongo.Client { } return client } - -func PushDocument(location Location, document interface{}) error { - client := GetNewClient() - collection := client.Database(location.Database).Collection(location.Collection) - defer client.Disconnect(context.TODO()) - - _, err := collection.InsertOne(context.TODO(), document) - if err != nil { - return err - } - return nil -} - -func GetDocuments(location Location, filter interface{}) ([][]byte, error) { - client := GetNewClient() - collection := client.Database(location.Database).Collection(location.Collection) - defer client.Disconnect(context.TODO()) - - var results [][]byte - - cursor, err := collection.Find(context.TODO(), filter) - if err != nil { - return results, errors.New("something is wrong with filter to find the document.") - } - for cursor.TryNext(context.TODO()) { - results = append(results, cursor.Current) - } - return results, nil -} - -func GetUniqueDocument(location Location, filter interface{}) ([]byte, error) { - client := GetNewClient() - collection := client.Database(location.Database).Collection(location.Collection) - defer client.Disconnect(context.TODO()) - - singleResult := collection.FindOne(context.TODO(), filter) - if singleResult.Err() != nil { - return nil, errors.New("no document was found, check the filter or the location.") - } - return singleResult.DecodeBytes() -} - -func DeleteDocument(location Location, filter interface{}) (int64, error) { - client := GetNewClient() - collection := client.Database(location.Database).Collection(location.Collection) - defer client.Disconnect(context.TODO()) - - result, err := collection.DeleteOne(context.TODO(), filter) - if err != nil { - return 0, errors.New("something is wrong with filter to delete the document.") - } - - return result.DeletedCount, nil -} - -func EditDocument(location Location, jsons DocumentUpdate) (int64, error) { - client := GetNewClient() - collection := client.Database(location.Database).Collection(location.Collection) - defer client.Disconnect(context.TODO()) - - result, err := collection.UpdateOne(context.TODO(), jsons.Filter, gin.H{"$set": jsons.Update}) - if err != nil { - return 0, err - } - if result.MatchedCount == 0 { - return 0, errors.New("cannot find document matching filter.") - } - - return result.ModifiedCount, nil -} diff --git a/internal/database/delete.go b/internal/database/delete.go new file mode 100644 index 0000000..0669b20 --- /dev/null +++ b/internal/database/delete.go @@ -0,0 +1,19 @@ +package database + +import ( + "context" + "errors" +) + +func DeleteDocument(location Location, filter interface{}) (int64, error) { + client := GetNewClient() + collection := client.Database(location.Database).Collection(location.Collection) + defer client.Disconnect(context.TODO()) + + result, err := collection.DeleteOne(context.TODO(), filter) + if err != nil { + return 0, errors.New("something is wrong with filter to delete the document.") + } + + return result.DeletedCount, nil +} diff --git a/internal/database/edit.go b/internal/database/edit.go new file mode 100644 index 0000000..11ae8db --- /dev/null +++ b/internal/database/edit.go @@ -0,0 +1,29 @@ +package database + +import ( + "context" + "errors" + + "github.com/gin-gonic/gin" +) + +type DocumentUpdate struct { + Filter gin.H `json:"filter"` + Update gin.H `json:"update"` +} + +func EditDocument(location Location, jsons DocumentUpdate) (int64, error) { + client := GetNewClient() + collection := client.Database(location.Database).Collection(location.Collection) + defer client.Disconnect(context.TODO()) + + result, err := collection.UpdateOne(context.TODO(), jsons.Filter, gin.H{"$set": jsons.Update}) + if err != nil { + return 0, err + } + if result.MatchedCount == 0 { + return 0, errors.New("cannot find document matching filter.") + } + + return result.ModifiedCount, nil +} diff --git a/internal/database/get.go b/internal/database/get.go new file mode 100644 index 0000000..e53e806 --- /dev/null +++ b/internal/database/get.go @@ -0,0 +1,35 @@ +package database + +import ( + "context" + "errors" +) + +func GetDocuments(location Location, filter interface{}) ([][]byte, error) { + client := GetNewClient() + collection := client.Database(location.Database).Collection(location.Collection) + defer client.Disconnect(context.TODO()) + + var results [][]byte + + cursor, err := collection.Find(context.TODO(), filter) + if err != nil { + return results, errors.New("something is wrong with filter to find the document.") + } + for cursor.TryNext(context.TODO()) { + results = append(results, cursor.Current) + } + return results, nil +} + +func GetUniqueDocument(location Location, filter interface{}) ([]byte, error) { + client := GetNewClient() + collection := client.Database(location.Database).Collection(location.Collection) + defer client.Disconnect(context.TODO()) + + singleResult := collection.FindOne(context.TODO(), filter) + if singleResult.Err() != nil { + return nil, errors.New("no document was found, check the filter or the location.") + } + return singleResult.DecodeBytes() +} diff --git a/internal/database/push.go b/internal/database/push.go new file mode 100644 index 0000000..370cd81 --- /dev/null +++ b/internal/database/push.go @@ -0,0 +1,15 @@ +package database + +import "context" + +func PushDocument(location Location, document interface{}) error { + client := GetNewClient() + collection := client.Database(location.Database).Collection(location.Collection) + defer client.Disconnect(context.TODO()) + + _, err := collection.InsertOne(context.TODO(), document) + if err != nil { + return err + } + return nil +} From c681b5389c89cc9844b5ed9ace77ce4dbe2f321a Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 21:00:32 +0100 Subject: [PATCH 06/18] Added codes in API responses --- internal/api/messages.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/messages.go b/internal/api/messages.go index 301f7c7..539a819 100644 --- a/internal/api/messages.go +++ b/internal/api/messages.go @@ -7,13 +7,13 @@ import ( ) func SendBadRequest(c *gin.Context, message string) { - c.JSON(http.StatusBadRequest, gin.H{"message": message}) + c.JSON(http.StatusBadRequest, gin.H{"message": message, "code": 400}) } func SendOk(c *gin.Context, message string) { - c.JSON(http.StatusOK, gin.H{"message": message}) + c.JSON(http.StatusOK, gin.H{"message": message, "code": 200}) } func SendForbidden(c *gin.Context, message string) { - c.JSON(http.StatusForbidden, gin.H{"message": message}) + c.JSON(http.StatusForbidden, gin.H{"message": message, "code": 403}) } From 3376d88f5674fc6e645815428f1e79257d398b20 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 18 Jan 2023 23:33:34 +0100 Subject: [PATCH 07/18] Splat handler in their own files, added pagination --- README.md | 8 ++- cmd/main.go | 10 ++-- internal/articles/add.go | 39 +++++++++++++++ internal/articles/delete.go | 25 ++++++++++ internal/articles/edit.go | 29 +++++++++++ internal/articles/get.go | 80 ++++++++++++++++++++++++++++++ internal/articles/handlers.go | 93 ----------------------------------- 7 files changed, 185 insertions(+), 99 deletions(-) create mode 100644 internal/articles/add.go create mode 100644 internal/articles/delete.go create mode 100644 internal/articles/edit.go create mode 100644 internal/articles/get.go delete mode 100644 internal/articles/handlers.go diff --git a/README.md b/README.md index 620fb36..3d22f18 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ The features are not complete, not tested and subject to many changes. ## Setup +Don't forget to setup the [Environment variables](#environment-variables)! + ### Use with Docker To **run** the app, run the command @@ -18,7 +20,7 @@ docker-compose up TODO -## Environment variables +### Environment variables - ./env - APP_API_ADDRESS: `http://example.com` @@ -29,6 +31,10 @@ TODO - APP_FRONT_PORT: `1234` - APP_JWT_SECRET: `secret` +## API Usage + +TODO + ## Demo TODO diff --git a/cmd/main.go b/cmd/main.go index eb38be8..7719433 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -63,11 +63,11 @@ func initArticlesRoutes(r *gin.RouterGroup) { articlesRouter := r.Group("/articles") articlesRouter.Use(corsMiddleware, api.AuthMiddleware.MiddlewareFunc()) - articlesRouter.GET("/", articles.GetArticleHandler) - articlesRouter.GET("/:id", articles.GetArticleHandler) - articlesRouter.POST("/:id", articles.AddArticleHandler) - articlesRouter.PUT("/:id", articles.EditArticleHandler) - articlesRouter.DELETE("/:id", articles.DeleteArticleHandler) + articlesRouter.GET("/", articles.Get) + articlesRouter.GET("/:id", articles.Get) + articlesRouter.POST("/:id", articles.Add) + articlesRouter.PUT("/:id", articles.Edit) + articlesRouter.DELETE("/:id", articles.Delete) } func initGin() { diff --git a/internal/articles/add.go b/internal/articles/add.go new file mode 100644 index 0000000..a564fb8 --- /dev/null +++ b/internal/articles/add.go @@ -0,0 +1,39 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" +) + +func Add(c *gin.Context) { + var article Article + article.TitleID = c.Params.ByName("id") + if c.BindJSON(&article) != nil { + api.SendBadRequest(c, "Could not correctly parse the article.") + return + } + + document, err := bson.Marshal(article) + if err != nil { + api.SendBadRequest(c, "Could not correctly marshal the article.") + return + } + + documents, _ := database.GetDocuments(articlesLocation, map[string]any{}) + if IsArticleIdAlreadyUsed(article.TitleID, documents) { + api.SendBadRequest(c, fmt.Sprintf("Article ID '%v' already used.", article.TitleID)) + return + } + + err = database.PushDocument(articlesLocation, document) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not insert document(s) into DB: %v`, err.Error())) + return + } + + api.SendOk(c, "Article successfully added!") +} diff --git a/internal/articles/delete.go b/internal/articles/delete.go new file mode 100644 index 0000000..835e209 --- /dev/null +++ b/internal/articles/delete.go @@ -0,0 +1,25 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func Delete(c *gin.Context) { + id := c.Params.ByName("id") + + deleteCount, err := database.DeleteDocument(articlesLocation, map[string]any{"titleID": id}) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not delete document(s) from DB: %v`, err.Error())) + return + } + + if deleteCount != 0 { + api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully deleted!", deleteCount)) + } else { + api.SendOk(c, "No articles were deleted.") + } +} diff --git a/internal/articles/edit.go b/internal/articles/edit.go new file mode 100644 index 0000000..f08138a --- /dev/null +++ b/internal/articles/edit.go @@ -0,0 +1,29 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func Edit(c *gin.Context) { + id := c.Params.ByName("id") + + var articleUpdate database.DocumentUpdate + articleUpdate.Filter = map[string]any{"titleID": id} + c.BindJSON(&articleUpdate.Update) + + editCount, err := database.EditDocument(articlesLocation, articleUpdate) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not edit document(s) from DB: %v`, err.Error())) + return + } + + if editCount != 0 { + api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully edited!", editCount)) + } else { + api.SendOk(c, "No articles were edited.") + } +} diff --git a/internal/articles/get.go b/internal/articles/get.go new file mode 100644 index 0000000..1f6f531 --- /dev/null +++ b/internal/articles/get.go @@ -0,0 +1,80 @@ +package articles + +import ( + "fmt" + "net/http" + "os" + "strconv" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func getApiFullUrl() string { + return fmt.Sprintf("%v%v", os.Getenv("APP_API_ADDRESS"), os.Getenv("APP_BASE_API_PATH")) +} + +func getArticleSkipTakeFullUrl(skip uint64, take uint64) string { + return fmt.Sprintf("%v/articles?skip=%v&take=%v", getApiFullUrl(), skip+take, take) +} + +func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[string]any { + slicedArticles := articles[skip : skip+take] + return map[string]any{ + "content": slicedArticles, + "total": len(slicedArticles), + "pagination": map[string]any{ + "skip": skip, + "take": take, + "links": map[string]any{ + "next": getArticleSkipTakeFullUrl(skip+take, take), + "previous": getArticleSkipTakeFullUrl(skip-take, take), + }, + }, + } +} + +func parseUintQueryParam(c *gin.Context, param string) (uint64, error) { + value, err := strconv.ParseUint(c.Query(param), 10, 0) + if err != nil { + c.JSON(http.StatusBadRequest, fmt.Sprintf("%s query parameter must be a positive number.", param)) + } + return value, err +} + +func getArticleIdFilter(titleID string) map[string]any { + filter := map[string]any{} + if titleID != "" { + filter["titleID"] = titleID + } + return filter +} + +func Get(c *gin.Context) { + titleID := c.Params.ByName("id") + + skip, skipErr := parseUintQueryParam(c, "skip") + take, takeErr := parseUintQueryParam(c, "take") + if skipErr != nil || takeErr != nil { + return + } + + articles, err := database.GetDocuments(articlesLocation, getArticleIdFilter(titleID)) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf("The ID '%v' doesn't match any article.", titleID)) + return + } + + articlesArray := ParseArticlesFromBytesToArray(articles) + articlesArrayLength := uint64(len(articlesArray)) + if skip >= articlesArrayLength || take == 0 { + c.JSON(http.StatusOK, map[string]any{ + "content": []string{}, + "total": "0", + }) + return + } + + c.JSON(http.StatusOK, getBuiltGetResponse(articlesArray, skip, take)) +} diff --git a/internal/articles/handlers.go b/internal/articles/handlers.go deleted file mode 100644 index 55856e4..0000000 --- a/internal/articles/handlers.go +++ /dev/null @@ -1,93 +0,0 @@ -package articles - -import ( - "fmt" - "net/http" - - "github.com/Floriansylvain/GohCMS/internal/api" - "github.com/Floriansylvain/GohCMS/internal/database" - "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" -) - -func GetArticleHandler(c *gin.Context) { - articleID := c.Params.ByName("id") - - filter := gin.H{} - if articleID != "" { - filter["titleID"] = articleID - } - - articles, err := database.GetDocuments(articlesLocation, filter) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf("The ID '%v' doesn't match any article.", articleID)) - return - } - - c.JSON(http.StatusOK, ParseArticlesFromBytesToArray(articles)) -} - -func AddArticleHandler(c *gin.Context) { - var article Article - article.TitleID = c.Params.ByName("id") - if c.BindJSON(&article) != nil { - api.SendBadRequest(c, "Could not correctly parse the article.") - return - } - - document, err := bson.Marshal(article) - if err != nil { - api.SendBadRequest(c, "Could not correctly marshal the article.") - return - } - - documents, _ := database.GetDocuments(articlesLocation, gin.H{}) - if IsArticleIdAlreadyUsed(article.TitleID, documents) { - api.SendBadRequest(c, fmt.Sprintf("Article ID '%v' already used.", article.TitleID)) - return - } - - err = database.PushDocument(articlesLocation, document) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not insert document(s) into DB: %v`, err.Error())) - return - } - - api.SendOk(c, "Article successfully added!") -} - -func DeleteArticleHandler(c *gin.Context) { - id := c.Params.ByName("id") - - deleteCount, err := database.DeleteDocument(articlesLocation, gin.H{"titleID": id}) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not delete document(s) from DB: %v`, err.Error())) - return - } - - if deleteCount != 0 { - api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully deleted!", deleteCount)) - } else { - api.SendOk(c, "No articles were deleted.") - } -} - -func EditArticleHandler(c *gin.Context) { - id := c.Params.ByName("id") - - var articleUpdate database.DocumentUpdate - articleUpdate.Filter = gin.H{"titleID": id} - c.BindJSON(&articleUpdate.Update) - - editCount, err := database.EditDocument(articlesLocation, articleUpdate) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not edit document(s) from DB: %v`, err.Error())) - return - } - - if editCount != 0 { - api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully edited!", editCount)) - } else { - api.SendOk(c, "No articles were edited.") - } -} From 1ffc999e1ae39f0b853a4f6ac75ffb19d9cdec02 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 19 Jan 2023 00:19:52 +0100 Subject: [PATCH 08/18] Solved several pagination problems --- internal/articles/get.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/internal/articles/get.go b/internal/articles/get.go index 1f6f531..ad7d74e 100644 --- a/internal/articles/get.go +++ b/internal/articles/get.go @@ -16,11 +16,20 @@ func getApiFullUrl() string { } func getArticleSkipTakeFullUrl(skip uint64, take uint64) string { - return fmt.Sprintf("%v/articles?skip=%v&take=%v", getApiFullUrl(), skip+take, take) + return fmt.Sprintf("%varticles?skip=%v&take=%v", getApiFullUrl(), skip+take, take) } func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[string]any { - slicedArticles := articles[skip : skip+take] + articlesCap := uint64(len(articles)) + + var normTake uint64 + if skip+take > articlesCap { + normTake = articlesCap + } else { + normTake = skip + take + } + + slicedArticles := articles[skip:normTake] return map[string]any{ "content": slicedArticles, "total": len(slicedArticles), @@ -35,11 +44,11 @@ func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[strin } } -func parseUintQueryParam(c *gin.Context, param string) (uint64, error) { - value, err := strconv.ParseUint(c.Query(param), 10, 0) - if err != nil { - c.JSON(http.StatusBadRequest, fmt.Sprintf("%s query parameter must be a positive number.", param)) +func parseUintQueryParam(c *gin.Context, param string, defaultValue uint64) (uint64, error) { + if c.Query(param) == "" { + return defaultValue, nil } + value, err := strconv.ParseUint(c.Query(param), 10, 0) return value, err } @@ -54,9 +63,10 @@ func getArticleIdFilter(titleID string) map[string]any { func Get(c *gin.Context) { titleID := c.Params.ByName("id") - skip, skipErr := parseUintQueryParam(c, "skip") - take, takeErr := parseUintQueryParam(c, "take") + skip, skipErr := parseUintQueryParam(c, "skip", 0) + take, takeErr := parseUintQueryParam(c, "take", 10) if skipErr != nil || takeErr != nil { + api.SendBadRequest(c, "Take and Skip query parameters must be positive numbers.") return } From 587301b119e3b153333bc63a1b1c8707b66a46c5 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 19 Jan 2023 00:54:16 +0100 Subject: [PATCH 09/18] Fix Articles front before table pagination impl. --- web/admin-gui/src/utils/database.ts | 15 ++++++++++++++- web/admin-gui/src/views/Articles.vue | 2 +- web/admin-gui/src/views/ArticlesEdit.vue | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/web/admin-gui/src/utils/database.ts b/web/admin-gui/src/utils/database.ts index 5a0ddaf..4114451 100644 --- a/web/admin-gui/src/utils/database.ts +++ b/web/admin-gui/src/utils/database.ts @@ -11,7 +11,20 @@ export interface Article { online: boolean } -export async function getArticles(id: string): Promise> { +export interface GetArticle { + content: Article[], + total: number + pagination: { + skip: number, + take: number + links: { + next: string, + previous: string + } + } +} + +export async function getArticles(id: string): Promise { return await fetch(`${baseApiUrl}/articles/${id}`, { credentials: 'include', method: 'GET', diff --git a/web/admin-gui/src/views/Articles.vue b/web/admin-gui/src/views/Articles.vue index 4c6fc4f..05525cf 100644 --- a/web/admin-gui/src/views/Articles.vue +++ b/web/admin-gui/src/views/Articles.vue @@ -10,7 +10,7 @@ const table = ref('') const tabulator: Ref = ref(undefined) onMounted(async () => { - articles.value = await getArticles('') + articles.value = await (await getArticles('')).content tabulator.value = new Tabulator(table.value, { data: articles.value, reactiveData: true, diff --git a/web/admin-gui/src/views/ArticlesEdit.vue b/web/admin-gui/src/views/ArticlesEdit.vue index 1705836..4448a40 100644 --- a/web/admin-gui/src/views/ArticlesEdit.vue +++ b/web/admin-gui/src/views/ArticlesEdit.vue @@ -16,7 +16,7 @@ const errorModalDescription: Ref = ref("Quelque chose s'est mal passé.. onMounted(async () => { await getArticles(useRoute().params.articleID as string) .then(articleFetch => { - article.value = articleFetch[0] + article.value = articleFetch.content[0] editorData.value = article.value.content.html }) .catch(error => { From 94b42d7e344ae8cfdaab90dd1120dae0c4a14ca3 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 19 Jan 2023 01:00:17 +0100 Subject: [PATCH 10/18] Added await on postArticle before redirect --- web/admin-gui/src/views/ArticlesNew.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/admin-gui/src/views/ArticlesNew.vue b/web/admin-gui/src/views/ArticlesNew.vue index 175a47e..818026b 100644 --- a/web/admin-gui/src/views/ArticlesNew.vue +++ b/web/admin-gui/src/views/ArticlesNew.vue @@ -29,7 +29,7 @@ function isFormEmpty(): boolean { return title.value === '' || rawTags.value === '' } -function createArticle() { +async function createArticle() { const article: Article = { titleID: generateTitleID(title.value), title: title.value, @@ -40,7 +40,7 @@ function createArticle() { online: false, tags: tags.value } - postArticle(article) + await postArticle(article) router.push(`/articles/edit/${article.titleID}`) } From a4ee8f90ff978c70e2059c6928861b97af85ec00 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 19 Jan 2023 02:30:01 +0100 Subject: [PATCH 11/18] Article pagination frontend implementation --- internal/articles/get.go | 5 ++- web/admin-gui/src/assets/tabulator.min.css | 2 - .../src/assets/tabulator.min.css.map | 1 - web/admin-gui/src/views/Articles.vue | 45 +++++++++++++++---- 4 files changed, 40 insertions(+), 13 deletions(-) delete mode 100644 web/admin-gui/src/assets/tabulator.min.css delete mode 100644 web/admin-gui/src/assets/tabulator.min.css.map diff --git a/internal/articles/get.go b/internal/articles/get.go index ad7d74e..fa15961 100644 --- a/internal/articles/get.go +++ b/internal/articles/get.go @@ -2,6 +2,7 @@ package articles import ( "fmt" + "math" "net/http" "os" "strconv" @@ -30,9 +31,10 @@ func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[strin } slicedArticles := articles[skip:normTake] + total := len(slicedArticles) return map[string]any{ "content": slicedArticles, - "total": len(slicedArticles), + "total": total, "pagination": map[string]any{ "skip": skip, "take": take, @@ -41,6 +43,7 @@ func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[strin "previous": getArticleSkipTakeFullUrl(skip-take, take), }, }, + "last_page": math.Ceil(float64(articlesCap) / float64(take)), } } diff --git a/web/admin-gui/src/assets/tabulator.min.css b/web/admin-gui/src/assets/tabulator.min.css deleted file mode 100644 index 3c060cd..0000000 --- a/web/admin-gui/src/assets/tabulator.min.css +++ /dev/null @@ -1,2 +0,0 @@ -.tabulator{position:relative;border:1px solid #999;background-color:#888;font-size:14px;text-align:left;overflow:hidden;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{position:relative;box-sizing:border-box;width:100%;border-bottom:1px solid #999;background-color:#e6e6e6;color:#555;font-weight:700;white-space:nowrap;overflow:hidden;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{position:relative;overflow:hidden}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{display:inline-flex;position:relative;box-sizing:border-box;flex-direction:column;justify-content:flex-start;border-right:1px solid #aaa;background:#e6e6e6;text-align:left;vertical-align:bottom;overflow:hidden}.tabulator .tabulator-header .tabulator-col.tabulator-moving{position:absolute;border:1px solid #999;background:#cdcdcd;pointer-events:none}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;position:relative;padding:4px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{white-space:normal;text-overflow:clip}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{box-sizing:border-box;width:100%;border:1px solid #999;padding:1px;background:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{display:flex;align-items:center;position:absolute;top:0;bottom:0;right:4px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #bbb}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{position:relative;display:flex;border-top:1px solid #aaa;overflow:hidden;margin-right:-1px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{position:relative;box-sizing:border-box;margin-top:2px;width:100%;text-align:center}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{width:0;height:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{cursor:pointer;background-color:#cdcdcd}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #bbb}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #666}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-top:6px solid #555}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{writing-mode:vertical-rl;text-orientation:mixed;display:flex;align-items:center;justify-content:center}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-right:0;padding-bottom:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{justify-content:center;left:0;right:0;top:4px;bottom:auto}.tabulator .tabulator-header .tabulator-frozen{position:sticky;left:0;z-index:10}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{box-sizing:border-box;min-width:600%;background:#f3f3f3!important;border-top:1px solid #aaa;border-bottom:1px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{min-width:600%}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{position:relative;width:100%;white-space:nowrap;overflow:auto;-webkit-overflow-scrolling:touch}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{box-sizing:border-box;display:flex;align-items:center;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{display:inline-block;text-align:center;padding:10px;color:#ccc;font-weight:700;font-size:20px;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{position:relative;display:inline-block;background-color:#fff;white-space:nowrap;overflow:visible;color:#333}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{font-weight:700;background:#e2e2e2!important}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-footer{border-top:1px solid #999;background-color:#e6e6e6;color:#555;font-weight:700;white-space:nowrap;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator .tabulator-footer .tabulator-footer-contents{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{box-sizing:border-box;width:100%;text-align:left;background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;overflow:hidden}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{display:inline-block;background:#f3f3f3!important}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{margin-bottom:-5px;border-bottom:none}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{flex:1;text-align:right;color:#555;font-family:inherit;font-weight:inherit;font-size:inherit}.tabulator .tabulator-footer .tabulator-page-size{display:inline-block;margin:0 5px;padding:2px 5px;border:1px solid #aaa;border-radius:3px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{display:inline-block;margin:0 2px;padding:2px 5px;border:1px solid #aaa;border-radius:3px;background:hsla(0,0%,100%,.2)}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}.tabulator .tabulator-footer .tabulator-page:not(.disabled):hover{cursor:pointer;background:rgba(0,0,0,.2);color:#fff}.tabulator .tabulator-col-resize-handle{position:relative;display:inline-block;width:6px;margin-left:-3px;margin-right:-3px;z-index:10;vertical-align:middle}.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}.tabulator .tabulator-col-resize-handle:last-of-type{width:3px;margin-right:0}.tabulator .tabulator-alert{position:absolute;display:flex;align-items:center;top:0;left:0;z-index:100;height:100%;width:100%;background:rgba(0,0,0,.4);text-align:center}.tabulator .tabulator-alert .tabulator-alert-msg{display:inline-block;margin:0 auto;padding:10px 20px;border-radius:10px;background:#fff;font-weight:700;font-size:16px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{position:relative;box-sizing:border-box;min-height:22px;background-color:#fff}.tabulator-row.tabulator-row-even{background-color:#efefef}.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}.tabulator-row.tabulator-selected{background-color:#9abcea}.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}.tabulator-row.tabulator-row-moving{border:1px solid #000;background:#fff}.tabulator-row.tabulator-moving{position:absolute;border-top:1px solid #aaa;border-bottom:1px solid #aaa;pointer-events:none;z-index:15}.tabulator-row .tabulator-row-resize-handle{position:absolute;right:0;bottom:0;left:0;height:5px}.tabulator-row .tabulator-row-resize-handle.prev{top:0;bottom:auto}.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}.tabulator-row .tabulator-responsive-collapse{box-sizing:border-box;padding:5px;border-top:1px solid #aaa;border-bottom:1px solid #aaa}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{display:inline-block;position:relative;box-sizing:border-box;padding:4px;border-right:1px solid #aaa;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tabulator-row .tabulator-cell.tabulator-frozen{display:inline-block;position:sticky;left:0;background-color:inherit;z-index:10}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{border:1px;background:transparent;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{border:1px;background:transparent;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{display:inline-flex;align-items:center;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{width:100%;height:3px;margin-top:2px;background:#666}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{display:inline-block;vertical-align:middle;height:9px;width:7px;margin-top:-9px;margin-right:5px;border-bottom-left-radius:1px;border-left:2px solid #aaa;border-bottom:2px solid #aaa}.tabulator-row .tabulator-cell .tabulator-data-tree-control{display:inline-flex;justify-content:center;align-items:center;vertical-align:middle;height:11px;width:11px;margin-right:5px;border:1px solid #333;border-radius:2px;background:rgba(0,0,0,.1);overflow:hidden}.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{cursor:pointer;background:rgba(0,0,0,.2)}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{display:inline-block;position:relative;height:7px;width:1px;background:transparent}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{position:absolute;content:"";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{display:inline-block;position:relative;height:7px;width:1px;background:#333}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{position:absolute;content:"";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{display:inline-flex;align-items:center;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;height:15px;width:15px;border-radius:20px;background:#666;color:#fff;font-weight:700;font-size:1.1em}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{opacity:.7;cursor:pointer}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{display:inline-block;height:14px;width:14px;border-radius:14px}.tabulator-row.tabulator-group{box-sizing:border-box;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;padding:5px 5px 5px 10px;background:#ccc;font-weight:700;min-width:100%}.tabulator-row.tabulator-group:hover{cursor:pointer;background-color:rgba(0,0,0,.1)}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{margin-right:10px;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;border-bottom:0}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{display:inline-block;width:0;height:0;margin-right:16px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:0;border-left:6px solid #666;vertical-align:middle}.tabulator-row.tabulator-group span{margin-left:10px;color:#d00}.tabulator-popup-container{position:absolute;display:inline-block;box-sizing:border-box;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);font-size:14px;overflow-y:auto;-webkit-overflow-scrolling:touch;z-index:10000}.tabulator-popup{padding:5px;border-radius:3px}.tabulator-tooltip{max-width:Min(500px,100%);padding:3px 5px;border-radius:2px;box-shadow:none;font-size:12px;pointer-events:none}.tabulator-menu .tabulator-menu-item{position:relative;box-sizing:border-box;padding:5px 10px;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{cursor:pointer;background:#efefef}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{display:inline-block;position:absolute;top:calc(5px + .4em);right:10px;height:7px;width:7px;content:"";border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;vertical-align:top;transform:rotate(45deg)}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{max-height:200px;font-size:14px;overflow-y:auto;-webkit-overflow-scrolling:touch}.tabulator-edit-list .tabulator-edit-list-item{padding:4px;color:#333;outline:none}.tabulator-edit-list .tabulator-edit-list-item.active{color:#fff;background:#1d68cd}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}.tabulator-edit-list .tabulator-edit-list-item:hover{cursor:pointer;color:#fff;background:#1d68cd}.tabulator-edit-list .tabulator-edit-list-placeholder{padding:4px;color:#333;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;padding:6px 4px 4px;color:#333;font-weight:700}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{text-align:initial;direction:rtl}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{text-align:initial;border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-right:0;margin-left:-1px}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:0;padding-left:25px}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-right:initial;border-left:1px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{margin-right:0;margin-left:5px;border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-right:0;margin-left:5px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{width:3px;margin-left:0;margin-right:-3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{position:absolute;top:0;bottom:0;left:0;right:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{display:inline-block;vertical-align:middle;height:9px;width:7px;margin-top:-9px;margin-right:5px;border-bottom-left-radius:1px;border-left:2px solid #aaa;border-bottom:2px solid #aaa}.tabulator-print-table .tabulator-print-table-group{box-sizing:border-box;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;padding:5px 5px 5px 10px;background:#ccc;font-weight:700;min-width:100%}.tabulator-print-table .tabulator-print-table-group:hover{cursor:pointer;background-color:rgba(0,0,0,.1)}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{margin-right:10px;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;border-bottom:0}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{display:inline-block;width:0;height:0;margin-right:16px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:0;border-left:6px solid #666;vertical-align:middle}.tabulator-print-table .tabulator-print-table-group span{margin-left:10px;color:#d00}.tabulator-print-table .tabulator-data-tree-control{display:inline-flex;justify-content:center;align-items:center;vertical-align:middle;height:11px;width:11px;margin-right:5px;border:1px solid #333;border-radius:2px;background:rgba(0,0,0,.1);overflow:hidden}.tabulator-print-table .tabulator-data-tree-control:hover{cursor:pointer;background:rgba(0,0,0,.2)}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{display:inline-block;position:relative;height:7px;width:1px;background:transparent}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{position:absolute;content:"";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{display:inline-block;position:relative;height:7px;width:1px;background:#333}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{position:absolute;content:"";left:-3px;top:3px;height:1px;width:7px;background:#333} -/*# sourceMappingURL=tabulator.min.css.map */ \ No newline at end of file diff --git a/web/admin-gui/src/assets/tabulator.min.css.map b/web/admin-gui/src/assets/tabulator.min.css.map deleted file mode 100644 index 4bb250d..0000000 --- a/web/admin-gui/src/assets/tabulator.min.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["tabulator.scss"],"names":[],"mappings":"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","file":"tabulator.min.css","sourcesContent":[".tabulator {\n position: relative;\n border: 1px solid #999;\n background-color: #888;\n font-size: 14px;\n text-align: left;\n overflow: hidden;\n -webkit-transform: translateZ(0);\n -moz-transform: translateZ(0);\n -ms-transform: translateZ(0);\n -o-transform: translateZ(0);\n transform: translateZ(0); }\n .tabulator[tabulator-layout=\"fitDataFill\"] .tabulator-tableholder .tabulator-table {\n min-width: 100%; }\n .tabulator[tabulator-layout=\"fitDataTable\"] {\n display: inline-block; }\n .tabulator.tabulator-block-select {\n user-select: none; }\n .tabulator .tabulator-header {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n border-bottom: 1px solid #999;\n background-color: #e6e6e6;\n color: #555;\n font-weight: bold;\n white-space: nowrap;\n overflow: hidden;\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -o-user-select: none; }\n .tabulator .tabulator-header.tabulator-header-hidden {\n display: none; }\n .tabulator .tabulator-header .tabulator-header-contents {\n position: relative;\n overflow: hidden; }\n .tabulator .tabulator-header .tabulator-header-contents .tabulator-headers {\n display: inline-block; }\n .tabulator .tabulator-header .tabulator-col {\n display: inline-flex;\n position: relative;\n box-sizing: border-box;\n flex-direction: column;\n justify-content: flex-start;\n border-right: 1px solid #aaa;\n background: #e6e6e6;\n text-align: left;\n vertical-align: bottom;\n overflow: hidden; }\n .tabulator .tabulator-header .tabulator-col.tabulator-moving {\n position: absolute;\n border: 1px solid #999;\n background: #cdcdcd;\n pointer-events: none; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content {\n box-sizing: border-box;\n position: relative;\n padding: 4px; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button {\n padding: 0 8px; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover {\n cursor: pointer;\n opacity: .6; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder {\n position: relative; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title {\n box-sizing: border-box;\n width: 100%;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n vertical-align: bottom; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap {\n white-space: normal;\n text-overflow: initial; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor {\n box-sizing: border-box;\n width: 100%;\n border: 1px solid #999;\n padding: 1px;\n background: #fff; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button + .tabulator-title-editor {\n width: calc(100% - 22px); }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter {\n display: flex;\n align-items: center;\n position: absolute;\n top: 0;\n bottom: 0;\n right: 4px; }\n .tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid #bbb; }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols {\n position: relative;\n display: flex;\n border-top: 1px solid #aaa;\n overflow: hidden;\n margin-right: -1px; }\n .tabulator .tabulator-header .tabulator-col .tabulator-header-filter {\n position: relative;\n box-sizing: border-box;\n margin-top: 2px;\n width: 100%;\n text-align: center; }\n .tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea {\n height: auto !important; }\n .tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg {\n margin-top: 3px; }\n .tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear {\n width: 0;\n height: 0; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title {\n padding-right: 25px; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover {\n cursor: pointer;\n background-color: #cdcdcd; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"none\"] .tabulator-col-content .tabulator-col-sorter {\n color: #bbb; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"none\"] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover {\n cursor: pointer;\n border-bottom: 6px solid #555; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"none\"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {\n border-top: none;\n border-bottom: 6px solid #bbb; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"ascending\"] .tabulator-col-content .tabulator-col-sorter {\n color: #666; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"ascending\"] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover {\n cursor: pointer;\n border-bottom: 6px solid #555; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"ascending\"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {\n border-top: none;\n border-bottom: 6px solid #666; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"descending\"] .tabulator-col-content .tabulator-col-sorter {\n color: #666; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"descending\"] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover {\n cursor: pointer;\n border-top: 6px solid #555; }\n .tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=\"descending\"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {\n border-bottom: none;\n border-top: 6px solid #666;\n color: #666; }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n display: flex;\n align-items: center;\n justify-content: center; }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title {\n transform: rotate(180deg); }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title {\n padding-right: 0;\n padding-top: 20px; }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title {\n padding-right: 0;\n padding-bottom: 20px; }\n .tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter {\n justify-content: center;\n left: 0;\n right: 0;\n top: 4px;\n bottom: auto; }\n .tabulator .tabulator-header .tabulator-frozen {\n position: sticky;\n left: 0;\n z-index: 10; }\n .tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left {\n border-right: 2px solid #aaa; }\n .tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right {\n border-left: 2px solid #aaa; }\n .tabulator .tabulator-header .tabulator-calcs-holder {\n box-sizing: border-box;\n min-width: 600%;\n background: #f3f3f3 !important;\n border-top: 1px solid #aaa;\n border-bottom: 1px solid #aaa; }\n .tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row {\n background: #f3f3f3 !important; }\n .tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle {\n display: none; }\n .tabulator .tabulator-header .tabulator-frozen-rows-holder {\n min-width: 600%; }\n .tabulator .tabulator-header .tabulator-frozen-rows-holder:empty {\n display: none; }\n .tabulator .tabulator-tableholder {\n position: relative;\n width: 100%;\n white-space: nowrap;\n overflow: auto;\n -webkit-overflow-scrolling: touch; }\n .tabulator .tabulator-tableholder:focus {\n outline: none; }\n .tabulator .tabulator-tableholder .tabulator-placeholder {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%; }\n .tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=\"virtual\"] {\n min-height: 100%;\n min-width: 100%; }\n .tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents {\n display: inline-block;\n text-align: center;\n padding: 10px;\n color: #ccc;\n font-weight: bold;\n font-size: 20px;\n white-space: normal; }\n .tabulator .tabulator-tableholder .tabulator-table {\n position: relative;\n display: inline-block;\n background-color: #fff;\n white-space: nowrap;\n overflow: visible;\n color: #333; }\n .tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs {\n font-weight: bold;\n background: #e2e2e2 !important; }\n .tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top {\n border-bottom: 2px solid #aaa; }\n .tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom {\n border-top: 2px solid #aaa; }\n .tabulator .tabulator-footer {\n border-top: 1px solid #999;\n background-color: #e6e6e6;\n color: #555;\n font-weight: bold;\n white-space: nowrap;\n user-select: none;\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -o-user-select: none; }\n .tabulator .tabulator-footer .tabulator-footer-contents {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n padding: 5px 10px; }\n .tabulator .tabulator-footer .tabulator-footer-contents:empty {\n display: none; }\n .tabulator .tabulator-footer .tabulator-calcs-holder {\n box-sizing: border-box;\n width: 100%;\n text-align: left;\n background: #f3f3f3 !important;\n border-bottom: 1px solid #aaa;\n border-top: 1px solid #aaa;\n overflow: hidden; }\n .tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row {\n display: inline-block;\n background: #f3f3f3 !important; }\n .tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle {\n display: none; }\n .tabulator .tabulator-footer .tabulator-calcs-holder:only-child {\n margin-bottom: -5px;\n border-bottom: none; }\n .tabulator .tabulator-footer > * + .tabulator-page-counter {\n margin-left: 10px; }\n .tabulator .tabulator-footer .tabulator-page-counter {\n font-weight: normal; }\n .tabulator .tabulator-footer .tabulator-paginator {\n flex: 1;\n text-align: right;\n color: #555;\n font-family: inherit;\n font-weight: inherit;\n font-size: inherit; }\n .tabulator .tabulator-footer .tabulator-page-size {\n display: inline-block;\n margin: 0 5px;\n padding: 2px 5px;\n border: 1px solid #aaa;\n border-radius: 3px; }\n .tabulator .tabulator-footer .tabulator-pages {\n margin: 0 7px; }\n .tabulator .tabulator-footer .tabulator-page {\n display: inline-block;\n margin: 0 2px;\n padding: 2px 5px;\n border: 1px solid #aaa;\n border-radius: 3px;\n background: rgba(255, 255, 255, 0.2); }\n .tabulator .tabulator-footer .tabulator-page.active {\n color: #d00; }\n .tabulator .tabulator-footer .tabulator-page:disabled {\n opacity: .5; }\n .tabulator .tabulator-footer .tabulator-page:not(.disabled):hover {\n cursor: pointer;\n background: rgba(0, 0, 0, 0.2);\n color: #fff; }\n .tabulator .tabulator-col-resize-handle {\n position: relative;\n display: inline-block;\n width: 6px;\n margin-left: -3px;\n margin-right: -3px;\n z-index: 10;\n vertical-align: middle; }\n .tabulator .tabulator-col-resize-handle:hover {\n cursor: ew-resize; }\n .tabulator .tabulator-col-resize-handle:last-of-type {\n width: 3px;\n margin-right: 0; }\n .tabulator .tabulator-alert {\n position: absolute;\n display: flex;\n align-items: center;\n top: 0;\n left: 0;\n z-index: 100;\n height: 100%;\n width: 100%;\n background: rgba(0, 0, 0, 0.4);\n text-align: center; }\n .tabulator .tabulator-alert .tabulator-alert-msg {\n display: inline-block;\n margin: 0 auto;\n padding: 10px 20px;\n border-radius: 10px;\n background: #fff;\n font-weight: bold;\n font-size: 16px; }\n .tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg {\n border: 4px solid #333;\n color: #000; }\n .tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error {\n border: 4px solid #D00;\n color: #590000; }\n\n.tabulator-row {\n position: relative;\n box-sizing: border-box;\n min-height: 22px;\n background-color: #fff; }\n .tabulator-row.tabulator-row-even {\n background-color: #EFEFEF; }\n .tabulator-row.tabulator-selectable:hover {\n background-color: #bbb;\n cursor: pointer; }\n .tabulator-row.tabulator-selected {\n background-color: #9ABCEA; }\n .tabulator-row.tabulator-selected:hover {\n background-color: #769BCC;\n cursor: pointer; }\n .tabulator-row.tabulator-row-moving {\n border: 1px solid #000;\n background: #fff; }\n .tabulator-row.tabulator-moving {\n position: absolute;\n border-top: 1px solid #aaa;\n border-bottom: 1px solid #aaa;\n pointer-events: none;\n z-index: 15; }\n .tabulator-row .tabulator-row-resize-handle {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 5px; }\n .tabulator-row .tabulator-row-resize-handle.prev {\n top: 0;\n bottom: auto; }\n .tabulator-row .tabulator-row-resize-handle:hover {\n cursor: ns-resize; }\n .tabulator-row .tabulator-responsive-collapse {\n box-sizing: border-box;\n padding: 5px;\n border-top: 1px solid #aaa;\n border-bottom: 1px solid #aaa; }\n .tabulator-row .tabulator-responsive-collapse:empty {\n display: none; }\n .tabulator-row .tabulator-responsive-collapse table {\n font-size: 14px; }\n .tabulator-row .tabulator-responsive-collapse table tr td {\n position: relative; }\n .tabulator-row .tabulator-responsive-collapse table tr td:first-of-type {\n padding-right: 10px; }\n .tabulator-row .tabulator-cell {\n display: inline-block;\n position: relative;\n box-sizing: border-box;\n padding: 4px;\n border-right: 1px solid #aaa;\n vertical-align: middle;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis; }\n .tabulator-row .tabulator-cell.tabulator-frozen {\n display: inline-block;\n position: sticky;\n left: 0;\n background-color: inherit;\n z-index: 10; }\n .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {\n border-right: 2px solid #aaa; }\n .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right {\n border-left: 2px solid #aaa; }\n .tabulator-row .tabulator-cell.tabulator-editing {\n border: 1px solid #1D68CD;\n outline: none;\n padding: 0; }\n .tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select {\n border: 1px;\n background: transparent;\n outline: none; }\n .tabulator-row .tabulator-cell.tabulator-validation-fail {\n border: 1px solid #dd0000; }\n .tabulator-row .tabulator-cell.tabulator-validation-fail input, .tabulator-row .tabulator-cell.tabulator-validation-fail select {\n border: 1px;\n background: transparent;\n color: #dd0000; }\n .tabulator-row .tabulator-cell.tabulator-row-handle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -o-user-select: none; }\n .tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box {\n width: 80%; }\n .tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar {\n width: 100%;\n height: 3px;\n margin-top: 2px;\n background: #666; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-branch {\n display: inline-block;\n vertical-align: middle;\n height: 9px;\n width: 7px;\n margin-top: -9px;\n margin-right: 5px;\n border-bottom-left-radius: 1px;\n border-left: 2px solid #aaa;\n border-bottom: 2px solid #aaa; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n vertical-align: middle;\n height: 11px;\n width: 11px;\n margin-right: 5px;\n border: 1px solid #333;\n border-radius: 2px;\n background: rgba(0, 0, 0, 0.1);\n overflow: hidden; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control:hover {\n cursor: pointer;\n background: rgba(0, 0, 0, 0.2); }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse {\n display: inline-block;\n position: relative;\n height: 7px;\n width: 1px;\n background: transparent; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after {\n position: absolute;\n content: \"\";\n left: -3px;\n top: 3px;\n height: 1px;\n width: 7px;\n background: #333; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand {\n display: inline-block;\n position: relative;\n height: 7px;\n width: 1px;\n background: #333; }\n .tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after {\n position: absolute;\n content: \"\";\n left: -3px;\n top: 3px;\n height: 1px;\n width: 7px;\n background: #333; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -o-user-select: none;\n height: 15px;\n width: 15px;\n border-radius: 20px;\n background: #666;\n color: #fff;\n font-weight: bold;\n font-size: 1.1em; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover {\n opacity: .7;\n cursor: pointer; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close {\n display: initial; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open {\n display: none; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg {\n stroke: #fff; }\n .tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close {\n display: none; }\n .tabulator-row .tabulator-cell .tabulator-traffic-light {\n display: inline-block;\n height: 14px;\n width: 14px;\n border-radius: 14px; }\n .tabulator-row.tabulator-group {\n box-sizing: border-box;\n border-bottom: 1px solid #999;\n border-right: 1px solid #aaa;\n border-top: 1px solid #999;\n padding: 5px;\n padding-left: 10px;\n background: #ccc;\n font-weight: bold;\n min-width: 100%; }\n .tabulator-row.tabulator-group:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.1); }\n .tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow {\n margin-right: 10px;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid #666;\n border-bottom: 0; }\n .tabulator-row.tabulator-group.tabulator-group-level-1 {\n padding-left: 30px; }\n .tabulator-row.tabulator-group.tabulator-group-level-2 {\n padding-left: 50px; }\n .tabulator-row.tabulator-group.tabulator-group-level-3 {\n padding-left: 70px; }\n .tabulator-row.tabulator-group.tabulator-group-level-4 {\n padding-left: 90px; }\n .tabulator-row.tabulator-group.tabulator-group-level-5 {\n padding-left: 110px; }\n .tabulator-row.tabulator-group .tabulator-group-toggle {\n display: inline-block; }\n .tabulator-row.tabulator-group .tabulator-arrow {\n display: inline-block;\n width: 0;\n height: 0;\n margin-right: 16px;\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-right: 0;\n border-left: 6px solid #666;\n vertical-align: middle; }\n .tabulator-row.tabulator-group span {\n margin-left: 10px;\n color: #d00; }\n\n.tabulator-popup-container {\n position: absolute;\n display: inline-block;\n box-sizing: border-box;\n background: #fff;\n border: 1px solid #aaa;\n box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);\n font-size: 14px;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n z-index: 10000; }\n\n.tabulator-popup {\n padding: 5px;\n border-radius: 3px; }\n\n.tabulator-tooltip {\n max-width: Min(500px, 100%);\n padding: 3px 5px;\n border-radius: 2px;\n box-shadow: none;\n font-size: 12px;\n pointer-events: none; }\n\n.tabulator-menu .tabulator-menu-item {\n position: relative;\n box-sizing: border-box;\n padding: 5px 10px;\n user-select: none; }\n .tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled {\n opacity: .5; }\n .tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover {\n cursor: pointer;\n background: #EFEFEF; }\n .tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu {\n padding-right: 25px; }\n .tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu::after {\n display: inline-block;\n position: absolute;\n top: calc(5px + .4em);\n right: 10px;\n height: 7px;\n width: 7px;\n content: '';\n border-width: 1px 1px 0 0;\n border-style: solid;\n border-color: #aaa;\n vertical-align: top;\n transform: rotate(45deg); }\n\n.tabulator-menu .tabulator-menu-separator {\n border-top: 1px solid #aaa; }\n\n.tabulator-edit-list {\n max-height: 200px;\n font-size: 14px;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch; }\n .tabulator-edit-list .tabulator-edit-list-item {\n padding: 4px;\n color: #333;\n outline: none; }\n .tabulator-edit-list .tabulator-edit-list-item.active {\n color: #fff;\n background: #1D68CD; }\n .tabulator-edit-list .tabulator-edit-list-item.active.focused {\n outline: 1px solid rgba(255, 255, 255, 0.5); }\n .tabulator-edit-list .tabulator-edit-list-item.focused {\n outline: 1px solid #1D68CD; }\n .tabulator-edit-list .tabulator-edit-list-item:hover {\n cursor: pointer;\n color: #fff;\n background: #1D68CD; }\n .tabulator-edit-list .tabulator-edit-list-placeholder {\n padding: 4px;\n color: #333;\n text-align: center; }\n .tabulator-edit-list .tabulator-edit-list-group {\n border-bottom: 1px solid #aaa;\n padding: 4px;\n padding-top: 6px;\n color: #333;\n font-weight: bold; }\n .tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2, .tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2 {\n padding-left: 12px; }\n .tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3, .tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3 {\n padding-left: 20px; }\n .tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4, .tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4 {\n padding-left: 28px; }\n .tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5, .tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5 {\n padding-left: 36px; }\n\n.tabulator.tabulator-ltr {\n direction: ltr; }\n\n.tabulator.tabulator-rtl {\n text-align: initial;\n direction: rtl; }\n .tabulator.tabulator-rtl .tabulator-header .tabulator-col {\n text-align: initial;\n border-left: 1px solid #aaa;\n border-right: initial; }\n .tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols {\n margin-right: initial;\n margin-left: -1px; }\n .tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title {\n padding-right: 0;\n padding-left: 25px; }\n .tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter {\n left: 8px;\n right: initial; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-cell {\n border-right: initial;\n border-left: 1px solid #aaa; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch {\n margin-right: initial;\n margin-left: 5px;\n border-bottom-left-radius: initial;\n border-bottom-right-radius: 1px;\n border-left: initial;\n border-right: 2px solid #aaa; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control {\n margin-right: initial;\n margin-left: 5px; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {\n border-left: 2px solid #aaa; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right {\n border-right: 2px solid #aaa; }\n .tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type {\n width: 3px;\n margin-left: 0;\n margin-right: -3px; }\n .tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder {\n text-align: initial; }\n\n.tabulator-print-fullscreen {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 10000; }\n\nbody.tabulator-print-fullscreen-hide > *:not(.tabulator-print-fullscreen) {\n display: none !important; }\n\n.tabulator-print-table {\n border-collapse: collapse; }\n .tabulator-print-table .tabulator-data-tree-branch {\n display: inline-block;\n vertical-align: middle;\n height: 9px;\n width: 7px;\n margin-top: -9px;\n margin-right: 5px;\n border-bottom-left-radius: 1px;\n border-left: 2px solid #aaa;\n border-bottom: 2px solid #aaa; }\n .tabulator-print-table .tabulator-print-table-group {\n box-sizing: border-box;\n border-bottom: 1px solid #999;\n border-right: 1px solid #aaa;\n border-top: 1px solid #999;\n padding: 5px;\n padding-left: 10px;\n background: #ccc;\n font-weight: bold;\n min-width: 100%; }\n .tabulator-print-table .tabulator-print-table-group:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.1); }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow {\n margin-right: 10px;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid #666;\n border-bottom: 0; }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td {\n padding-left: 30px !important; }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td {\n padding-left: 50px !important; }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td {\n padding-left: 70px !important; }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td {\n padding-left: 90px !important; }\n .tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td {\n padding-left: 110px !important; }\n .tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle {\n display: inline-block; }\n .tabulator-print-table .tabulator-print-table-group .tabulator-arrow {\n display: inline-block;\n width: 0;\n height: 0;\n margin-right: 16px;\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-right: 0;\n border-left: 6px solid #666;\n vertical-align: middle; }\n .tabulator-print-table .tabulator-print-table-group span {\n margin-left: 10px;\n color: #d00; }\n .tabulator-print-table .tabulator-data-tree-control {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n vertical-align: middle;\n height: 11px;\n width: 11px;\n margin-right: 5px;\n border: 1px solid #333;\n border-radius: 2px;\n background: rgba(0, 0, 0, 0.1);\n overflow: hidden; }\n .tabulator-print-table .tabulator-data-tree-control:hover {\n cursor: pointer;\n background: rgba(0, 0, 0, 0.2); }\n .tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse {\n display: inline-block;\n position: relative;\n height: 7px;\n width: 1px;\n background: transparent; }\n .tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after {\n position: absolute;\n content: \"\";\n left: -3px;\n top: 3px;\n height: 1px;\n width: 7px;\n background: #333; }\n .tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand {\n display: inline-block;\n position: relative;\n height: 7px;\n width: 1px;\n background: #333; }\n .tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after {\n position: absolute;\n content: \"\";\n left: -3px;\n top: 3px;\n height: 1px;\n width: 7px;\n background: #333; }\n"]} \ No newline at end of file diff --git a/web/admin-gui/src/views/Articles.vue b/web/admin-gui/src/views/Articles.vue index 05525cf..e982e51 100644 --- a/web/admin-gui/src/views/Articles.vue +++ b/web/admin-gui/src/views/Articles.vue @@ -1,20 +1,29 @@ @@ -81,6 +106,8 @@ main { flex-direction: column; gap: 16px; + height: 100%; + padding: 32px; } @@ -90,7 +117,7 @@ main>a { #table { width: 100%; - + height: 100%; margin: auto; } From 9b4ad81122782a55baee824032a0270cb7cd4e6a Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 19 Jan 2023 03:05:16 +0100 Subject: [PATCH 12/18] Added delete button business --- web/admin-gui/src/utils/database.ts | 10 +++++++++- web/admin-gui/src/views/Articles.vue | 13 ++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/web/admin-gui/src/utils/database.ts b/web/admin-gui/src/utils/database.ts index 4114451..7c9bd85 100644 --- a/web/admin-gui/src/utils/database.ts +++ b/web/admin-gui/src/utils/database.ts @@ -32,7 +32,15 @@ export async function getArticles(id: string): Promise { .then(result => result.json()) } -async function sendArticle(article: Article, method: 'POST' | 'PUT') { +export async function deleteArticle(id: string) { + return await fetch(`${baseApiUrl}/articles/${id}`, { + credentials: 'include', + method: 'DELETE', + }) + .then(result => result.json()) +} + +async function sendArticle(article: Article, method: 'POST' | 'PUT'): Promise { return await fetch(`${baseApiUrl}/articles/${article.titleID}`, { credentials: 'include', method, diff --git a/web/admin-gui/src/views/Articles.vue b/web/admin-gui/src/views/Articles.vue index e982e51..d33a66a 100644 --- a/web/admin-gui/src/views/Articles.vue +++ b/web/admin-gui/src/views/Articles.vue @@ -1,9 +1,11 @@ @@ -111,6 +126,12 @@ onMounted(async () => { + +