mirror of
https://github.com/usememos/memos.git
synced 2025-02-14 18:30:42 +01:00
feat: add database filesize in UI (#488)
This commit is contained in:
parent
706b1b428f
commit
a2831b37c4
@ -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"`
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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" {
|
||||
|
@ -21,6 +21,7 @@ function App() {
|
||||
globalService.initialState();
|
||||
}, []);
|
||||
|
||||
// Inject additional style and script codes.
|
||||
useEffect(() => {
|
||||
api.getSystemStatus().then(({ data }) => {
|
||||
const { data: status } = data;
|
||||
|
@ -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<State>({
|
||||
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 (
|
||||
<div className="section-container preferences-section-container">
|
||||
<div className="section-container system-section-container">
|
||||
<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>
|
||||
<Switch size="sm" checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} />
|
||||
</label>
|
||||
<div className="form-label selector">
|
||||
<div className="form-label">
|
||||
<span className="normal-text">Additional style</span>
|
||||
<Button size="sm" onClick={handleSaveAdditionalStyle}>
|
||||
Save
|
||||
@ -100,12 +116,13 @@ const SystemSection = () => {
|
||||
fontFamily: "monospace",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
minRows={5}
|
||||
minRows={4}
|
||||
maxRows={10}
|
||||
placeholder="Additional css codes"
|
||||
value={state.additionalStyle}
|
||||
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>
|
||||
<Button size="sm" onClick={handleSaveAdditionalScript}>
|
||||
Save
|
||||
@ -117,8 +134,9 @@ const SystemSection = () => {
|
||||
fontFamily: "monospace",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
minRows={5}
|
||||
minRows={4}
|
||||
maxRows={10}
|
||||
placeholder="Additional JavaScript codes"
|
||||
value={state.additionalScript}
|
||||
onChange={(event) => handleAdditionalScriptChanged(event.target.value)}
|
||||
/>
|
||||
|
@ -64,7 +64,6 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
toImage(memoElRef.current, {
|
||||
backgroundColor: "#eaeaea",
|
||||
pixelRatio: window.devicePixelRatio * 2,
|
||||
})
|
||||
.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 {
|
||||
@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 {
|
||||
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 {
|
||||
@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 {
|
||||
@ -72,11 +72,11 @@
|
||||
@apply w-64 flex flex-col justify-center items-start;
|
||||
|
||||
> .name-text {
|
||||
@apply text-lg truncate font-bold text-gray-600;
|
||||
@apply text-lg truncate font-medium text-gray-600;
|
||||
}
|
||||
|
||||
> .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": {
|
||||
"editing": "Editing...",
|
||||
"cancel-edit": "Cancel Edit",
|
||||
"cancel-edit": "Cancel edit",
|
||||
"save": "Save",
|
||||
"placeholder": "Any thoughts...",
|
||||
"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 {
|
||||
host: User;
|
||||
profile: Profile;
|
||||
dbSize: number;
|
||||
// System settings
|
||||
allowSignUp: boolean;
|
||||
additionalStyle: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user