diff --git a/api/system.go b/api/system.go index 41220741..525a29e0 100644 --- a/api/system.go +++ b/api/system.go @@ -5,6 +5,8 @@ import "github.com/usememos/memos/server/profile" type SystemStatus struct { Host *User `json:"host"` Profile *profile.Profile `json:"profile"` + DBSize int64 `json:"dbSize"` + // System settings // Allow sign up. AllowSignUp bool `json:"allowSignUp"` diff --git a/server/resource.go b/server/resource.go index 06cd77a4..14dbab82 100644 --- a/server/resource.go +++ b/server/resource.go @@ -16,6 +16,11 @@ import ( "github.com/labstack/echo/v4" ) +const ( + // The max file size is 32MB. + maxFileSize = (32 * 8) << 20 +) + func (s *Server) registerResourceRoutes(g *echo.Group) { g.POST("/resource", func(c echo.Context) error { ctx := c.Request().Context() @@ -24,13 +29,15 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } - err := c.Request().ParseMultipartForm(64 << 20) - if err != nil { + if err := c.Request().ParseMultipartForm(maxFileSize); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Upload file overload max size").SetInternal(err) } file, err := c.FormFile("file") if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get uploading file").SetInternal(err) + } + if file == nil { return echo.NewHTTPError(http.StatusBadRequest, "Upload file not found").SetInternal(err) } diff --git a/server/system.go b/server/system.go index 64ea9d14..889d37d6 100644 --- a/server/system.go +++ b/server/system.go @@ -3,9 +3,11 @@ package server import ( "encoding/json" "net/http" + "os" "github.com/usememos/memos/api" "github.com/usememos/memos/common" + metric "github.com/usememos/memos/plugin/metrics" "github.com/labstack/echo/v4" ) @@ -65,6 +67,12 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { } } + fi, err := os.Stat(s.Profile.DSN) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read database fileinfo").SetInternal(err) + } + systemStatus.DBSize = fi.Size() + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemStatus)); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode system status response").SetInternal(err) @@ -103,6 +111,10 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err) } + s.Collector.Collect(ctx, &metric.Metric{ + Name: "systemSetting updated", + Labels: map[string]string{"field": string(systemSettingUpsert.Name)}, + }) c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemSetting)); err != nil { diff --git a/server/version/version.go b/server/version/version.go index ea686e6a..4aa2858f 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -7,10 +7,10 @@ import ( // Version is the service current released version. // Semantic versioning: https://semver.org/ -var Version = "0.7.2" +var Version = "0.7.3" // DevVersion is the service current development version. -var DevVersion = "0.7.2" +var DevVersion = "0.7.3" func GetCurrentVersion(mode string) string { if mode == "dev" { diff --git a/web/src/App.tsx b/web/src/App.tsx index 2a94cf54..a45b5a0b 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -21,6 +21,7 @@ function App() { globalService.initialState(); }, []); + // Inject additional style and script codes. useEffect(() => { api.getSystemStatus().then(({ data }) => { const { data: status } = data; diff --git a/web/src/components/Settings/SystemSection.tsx b/web/src/components/Settings/SystemSection.tsx index b2258335..4140015b 100644 --- a/web/src/components/Settings/SystemSection.tsx +++ b/web/src/components/Settings/SystemSection.tsx @@ -3,17 +3,28 @@ import { useTranslation } from "react-i18next"; import { Button, Switch, Textarea } from "@mui/joy"; import * as api from "../../helpers/api"; import toastHelper from "../Toast"; -import "../../less/settings/preferences-section.less"; +import "../../less/settings/system-section.less"; interface State { + dbSize: number; allowSignUp: boolean; additionalStyle: string; additionalScript: string; } +const formatBytes = (bytes: number) => { + if (bytes <= 0) return "0 Bytes"; + const k = 1024, + dm = 2, + sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], + i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i]; +}; + const SystemSection = () => { const { t } = useTranslation(); const [state, setState] = useState({ + dbSize: 0, allowSignUp: false, additionalStyle: "", additionalScript: "", @@ -23,6 +34,7 @@ const SystemSection = () => { api.getSystemStatus().then(({ data }) => { const { data: status } = data; setState({ + dbSize: status.dbSize, allowSignUp: status.allowSignUp, additionalStyle: status.additionalStyle, additionalScript: status.additionalScript, @@ -82,13 +94,17 @@ const SystemSection = () => { }; return ( -
+

{t("common.basic")}

-