feat: add database filesize in UI (#488)

pull/489/head v0.7.3
boojack 3 years ago committed by GitHub
parent 706b1b428f
commit a2831b37c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,8 @@ import "github.com/usememos/memos/server/profile"
type SystemStatus struct { type SystemStatus struct {
Host *User `json:"host"` Host *User `json:"host"`
Profile *profile.Profile `json:"profile"` Profile *profile.Profile `json:"profile"`
DBSize int64 `json:"dbSize"`
// System settings // System settings
// Allow sign up. // Allow sign up.
AllowSignUp bool `json:"allowSignUp"` AllowSignUp bool `json:"allowSignUp"`

@ -16,6 +16,11 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
const (
// The max file size is 32MB.
maxFileSize = (32 * 8) << 20
)
func (s *Server) registerResourceRoutes(g *echo.Group) { func (s *Server) registerResourceRoutes(g *echo.Group) {
g.POST("/resource", func(c echo.Context) error { g.POST("/resource", func(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
@ -24,13 +29,15 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
} }
err := c.Request().ParseMultipartForm(64 << 20) if err := c.Request().ParseMultipartForm(maxFileSize); err != nil {
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Upload file overload max size").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Upload file overload max size").SetInternal(err)
} }
file, err := c.FormFile("file") file, err := c.FormFile("file")
if err != nil { 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) return echo.NewHTTPError(http.StatusBadRequest, "Upload file not found").SetInternal(err)
} }

@ -3,9 +3,11 @@ package server
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"os"
"github.com/usememos/memos/api" "github.com/usememos/memos/api"
"github.com/usememos/memos/common" "github.com/usememos/memos/common"
metric "github.com/usememos/memos/plugin/metrics"
"github.com/labstack/echo/v4" "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) c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemStatus)); err != nil { 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) 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 { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err) 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) c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemSetting)); err != nil { if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemSetting)); err != nil {

@ -7,10 +7,10 @@ import (
// Version is the service current released version. // Version is the service current released version.
// Semantic versioning: https://semver.org/ // Semantic versioning: https://semver.org/
var Version = "0.7.2" var Version = "0.7.3"
// DevVersion is the service current development version. // DevVersion is the service current development version.
var DevVersion = "0.7.2" var DevVersion = "0.7.3"
func GetCurrentVersion(mode string) string { func GetCurrentVersion(mode string) string {
if mode == "dev" { if mode == "dev" {

@ -21,6 +21,7 @@ function App() {
globalService.initialState(); globalService.initialState();
}, []); }, []);
// Inject additional style and script codes.
useEffect(() => { useEffect(() => {
api.getSystemStatus().then(({ data }) => { api.getSystemStatus().then(({ data }) => {
const { data: status } = data; const { data: status } = data;

@ -3,17 +3,28 @@ import { useTranslation } from "react-i18next";
import { Button, Switch, Textarea } from "@mui/joy"; import { Button, Switch, Textarea } from "@mui/joy";
import * as api from "../../helpers/api"; import * as api from "../../helpers/api";
import toastHelper from "../Toast"; import toastHelper from "../Toast";
import "../../less/settings/preferences-section.less"; import "../../less/settings/system-section.less";
interface State { interface State {
dbSize: number;
allowSignUp: boolean; allowSignUp: boolean;
additionalStyle: string; additionalStyle: string;
additionalScript: 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 SystemSection = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
dbSize: 0,
allowSignUp: false, allowSignUp: false,
additionalStyle: "", additionalStyle: "",
additionalScript: "", additionalScript: "",
@ -23,6 +34,7 @@ const SystemSection = () => {
api.getSystemStatus().then(({ data }) => { api.getSystemStatus().then(({ data }) => {
const { data: status } = data; const { data: status } = data;
setState({ setState({
dbSize: status.dbSize,
allowSignUp: status.allowSignUp, allowSignUp: status.allowSignUp,
additionalStyle: status.additionalStyle, additionalStyle: status.additionalStyle,
additionalScript: status.additionalScript, additionalScript: status.additionalScript,
@ -82,13 +94,17 @@ const SystemSection = () => {
}; };
return ( return (
<div className="section-container preferences-section-container"> <div className="section-container system-section-container">
<p className="title-text">{t("common.basic")}</p> <p className="title-text">{t("common.basic")}</p>
<label className="form-label selector"> <p className="text-value">
Database File Size: <span className="font-mono font-medium">{formatBytes(state.dbSize)}</span>
</p>
<p className="title-text">{t("sidebar.setting")}</p>
<label className="form-label">
<span className="normal-text">Allow user signup</span> <span className="normal-text">Allow user signup</span>
<Switch size="sm" checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} /> <Switch size="sm" checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} />
</label> </label>
<div className="form-label selector"> <div className="form-label">
<span className="normal-text">Additional style</span> <span className="normal-text">Additional style</span>
<Button size="sm" onClick={handleSaveAdditionalStyle}> <Button size="sm" onClick={handleSaveAdditionalStyle}>
Save Save
@ -100,12 +116,13 @@ const SystemSection = () => {
fontFamily: "monospace", fontFamily: "monospace",
fontSize: "14px", fontSize: "14px",
}} }}
minRows={5} minRows={4}
maxRows={10} maxRows={10}
placeholder="Additional css codes"
value={state.additionalStyle} value={state.additionalStyle}
onChange={(event) => handleAdditionalStyleChanged(event.target.value)} onChange={(event) => handleAdditionalStyleChanged(event.target.value)}
/> />
<div className="form-label selector mt-2"> <div className="form-label mt-2">
<span className="normal-text">Additional script</span> <span className="normal-text">Additional script</span>
<Button size="sm" onClick={handleSaveAdditionalScript}> <Button size="sm" onClick={handleSaveAdditionalScript}>
Save Save
@ -117,8 +134,9 @@ const SystemSection = () => {
fontFamily: "monospace", fontFamily: "monospace",
fontSize: "14px", fontSize: "14px",
}} }}
minRows={5} minRows={4}
maxRows={10} maxRows={10}
placeholder="Additional JavaScript codes"
value={state.additionalScript} value={state.additionalScript}
onChange={(event) => handleAdditionalScriptChanged(event.target.value)} onChange={(event) => handleAdditionalScriptChanged(event.target.value)}
/> />

@ -64,7 +64,6 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
} }
toImage(memoElRef.current, { toImage(memoElRef.current, {
backgroundColor: "#eaeaea",
pixelRatio: window.devicePixelRatio * 2, pixelRatio: window.devicePixelRatio * 2,
}) })
.then((url) => { .then((url) => {

@ -57,12 +57,3 @@ export function remove(keys: StorageKey[]) {
} }
} }
} }
export function emitStorageChangedEvent() {
const iframeEl = document.createElement("iframe");
iframeEl.style.display = "none";
document.body.appendChild(iframeEl);
iframeEl.contentWindow?.localStorage.setItem("t", Date.now().toString());
iframeEl.remove();
}

@ -1,5 +1,5 @@
.dialog-wrapper { .dialog-wrapper {
@apply fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all hide-scrollbar; @apply fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 pb-8 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all hide-scrollbar;
&.showup { &.showup {
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);

@ -0,0 +1,17 @@
.system-section-container {
> .title-text {
@apply mt-4 first:mt-1;
}
> .text-value {
@apply mr-2 text-sm;
}
> .form-label {
@apply mb-2 flex flex-row justify-between items-center;
> .normal-text {
@apply mr-2 text-sm;
}
}
}

@ -42,7 +42,7 @@
} }
> .time-text { > .time-text {
@apply w-full px-6 pt-5 pb-2 text-xs text-gray-500 bg-white; @apply w-full px-6 pt-5 pb-2 text-sm text-gray-500 bg-white;
} }
> .memo-content-wrapper { > .memo-content-wrapper {
@ -72,11 +72,11 @@
@apply w-64 flex flex-col justify-center items-start; @apply w-64 flex flex-col justify-center items-start;
> .name-text { > .name-text {
@apply text-lg truncate font-bold text-gray-600; @apply text-lg truncate font-medium text-gray-600;
} }
> .usage-text { > .usage-text {
@apply -mt-1 text-sm text-gray-400 font-medium; @apply -mt-1 text-sm font-normal text-gray-400;
} }
} }

@ -79,7 +79,7 @@
}, },
"editor": { "editor": {
"editing": "Editing...", "editing": "Editing...",
"cancel-edit": "Cancel Edit", "cancel-edit": "Cancel edit",
"save": "Save", "save": "Save",
"placeholder": "Any thoughts...", "placeholder": "Any thoughts...",
"only-image-supported": "Only image file supported.", "only-image-supported": "Only image file supported.",

@ -6,6 +6,7 @@ interface Profile {
interface SystemStatus { interface SystemStatus {
host: User; host: User;
profile: Profile; profile: Profile;
dbSize: number;
// System settings // System settings
allowSignUp: boolean; allowSignUp: boolean;
additionalStyle: string; additionalStyle: string;

Loading…
Cancel
Save