mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: add database filesize in UI (#488)
This commit is contained in:
@ -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);
|
||||||
|
17
web/src/less/settings/system-section.less
Normal file
17
web/src/less/settings/system-section.less
Normal file
@ -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.",
|
||||||
|
1
web/src/types/modules/system.d.ts
vendored
1
web/src/types/modules/system.d.ts
vendored
@ -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;
|
||||||
|
Reference in New Issue
Block a user