Startup queries, foreign keys
This commit is contained in:
parent
3280c2c440
commit
60f1882bec
14 changed files with 148 additions and 69 deletions
|
@ -12,5 +12,6 @@ var config = {
|
|||
enable_signup: true,
|
||||
enable_password_reset: false,
|
||||
enable_payments: true,
|
||||
enable_reserve_topics: true,
|
||||
disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"]
|
||||
};
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
"account_usage_title": "Usage",
|
||||
"account_usage_of_limit": "of {{limit}}",
|
||||
"account_usage_unlimited": "Unlimited",
|
||||
"account_usage_limits_reset_daily": "Usage limits are reset daily at midnight (UTC)",
|
||||
"account_usage_plan_title": "Account type",
|
||||
"account_usage_plan_code_default": "Default",
|
||||
"account_usage_plan_code_unlimited": "Unlimited",
|
||||
|
@ -189,7 +190,7 @@
|
|||
"account_usage_topics_title": "Reserved topics",
|
||||
"account_usage_attachment_storage_title": "Attachment storage",
|
||||
"account_usage_attachment_storage_subtitle": "{{filesize}} per file",
|
||||
"account_usage_basis_ip_description": "Usage stats and limits for this account are based on your IP address, so they may be shared with other users.",
|
||||
"account_usage_basis_ip_description": "Usage stats and limits for this account are based on your IP address, so they may be shared with other users. Limits shown above are approximates based on the existing rate limits.",
|
||||
"account_delete_title": "Delete account",
|
||||
"account_delete_description": "Permanently delete your account",
|
||||
"account_delete_dialog_description": "This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please type '{{username}}' in the text box below.",
|
||||
|
@ -243,7 +244,7 @@
|
|||
"prefs_appearance_title": "Appearance",
|
||||
"prefs_appearance_language_title": "Language",
|
||||
"prefs_reservations_title": "Reserved topics",
|
||||
"prefs_reservations_description": "You may reserve topic names for personal use here, and define access to a topic for other users.",
|
||||
"prefs_reservations_description": "You can reserve topic names for personal use here. Reserving a topic gives you ownership over the topic, and allows you to define access permissions for other users over the topic.",
|
||||
"prefs_reservations_add_button": "Add reserved topic",
|
||||
"prefs_reservations_edit_button": "Edit topic access",
|
||||
"prefs_reservations_delete_button": "Reset topic access",
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import routes from "../components/routes";
|
||||
|
||||
class Session {
|
||||
store(username, token) {
|
||||
localStorage.setItem("user", username);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
import {LinearProgress, Stack, useMediaQuery} from "@mui/material";
|
||||
import {LinearProgress, Link, Stack, useMediaQuery} from "@mui/material";
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from "@mui/material/Typography";
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
|
@ -21,7 +21,9 @@ import IconButton from "@mui/material/IconButton";
|
|||
import {useOutletContext} from "react-router-dom";
|
||||
import {formatBytes} from "../app/utils";
|
||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import db from "../app/db";
|
||||
|
||||
const Account = () => {
|
||||
if (!session.exists()) {
|
||||
|
@ -169,6 +171,15 @@ const Stats = () => {
|
|||
}
|
||||
const planCode = account.plan.code ?? "none";
|
||||
const normalize = (value, max) => Math.min(value / max * 100, 100);
|
||||
const barColor = (remaining, limit) => {
|
||||
if (account.role === "admin") {
|
||||
return "primary";
|
||||
} else if (limit > 0 && remaining === 0) {
|
||||
return "error";
|
||||
}
|
||||
return "primary";
|
||||
};
|
||||
|
||||
return (
|
||||
<Card sx={{p: 3}} aria-label={t("account_usage_title")}>
|
||||
<Typography variant="h5" sx={{marginBottom: 2}}>
|
||||
|
@ -180,20 +191,37 @@ const Stats = () => {
|
|||
{account.role === "admin"
|
||||
? <>{t("account_usage_unlimited")} <Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
|
||||
: t(`account_usage_plan_code_${planCode}`)}
|
||||
{config.enable_payments && account.plan.upgradeable &&
|
||||
<em>{" "}
|
||||
<Link onClick={() => {}}>Upgrade</Link>
|
||||
</em>
|
||||
}
|
||||
</div>
|
||||
</Pref>
|
||||
<Pref title={t("account_usage_topics_title")}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.topics}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.topics > 0 ? t("account_usage_of_limit", { limit: account.limits.topics }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.topics > 0 ? normalize(account.stats.topics, account.limits.topics) : 100}
|
||||
color={account?.role !== "admin" && account.stats.topics_remaining === 0 ? 'error' : 'primary'}
|
||||
/>
|
||||
{account.limits.topics > 0 &&
|
||||
<>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.topics}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.topics }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.topics > 0 ? normalize(account.stats.topics, account.limits.topics) : 100}
|
||||
color={barColor(account.stats.topics_remaining, account.limits.topics)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{account.limits.topics === 0 &&
|
||||
<em>No reserved topics for this account</em>
|
||||
}
|
||||
</Pref>
|
||||
<Pref title={t("account_usage_messages_title")}>
|
||||
<Pref title={
|
||||
<>
|
||||
{t("account_usage_messages_title")}
|
||||
<Tooltip title={t("account_usage_limits_reset_daily")}><span><InfoIcon/></span></Tooltip>
|
||||
</>
|
||||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
|
||||
|
@ -201,10 +229,15 @@ const Stats = () => {
|
|||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||
color={account?.role !== "admin" && account.stats.messages_remaining === 0 ? 'error' : 'primary'}
|
||||
color={account.role === "user" && account.stats.messages_remaining === 0 ? 'error' : 'primary'}
|
||||
/>
|
||||
</Pref>
|
||||
<Pref title={t("account_usage_emails_title")}>
|
||||
<Pref title={
|
||||
<>
|
||||
{t("account_usage_emails_title")}
|
||||
<Tooltip title={t("account_usage_limits_reset_daily")}><span><InfoIcon/></span></Tooltip>
|
||||
</>
|
||||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
|
||||
|
@ -215,7 +248,14 @@ const Stats = () => {
|
|||
color={account?.role !== "admin" && account.stats.emails_remaining === 0 ? 'error' : 'primary'}
|
||||
/>
|
||||
</Pref>
|
||||
<Pref title={t("account_usage_attachment_storage_title")} subtitle={account.role !== "admin" ? t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) }) : null}>
|
||||
<Pref title={
|
||||
<>
|
||||
{t("account_usage_attachment_storage_title")}
|
||||
{account.role === "user" &&
|
||||
<Tooltip title={t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) })}><span><InfoIcon/></span></Tooltip>
|
||||
}
|
||||
</>
|
||||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
|
||||
|
@ -236,6 +276,17 @@ const Stats = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const InfoIcon = () => {
|
||||
return (
|
||||
<InfoOutlinedIcon sx={{
|
||||
verticalAlign: "bottom",
|
||||
width: "18px",
|
||||
marginLeft: "4px",
|
||||
color: "gray"
|
||||
}}/>
|
||||
);
|
||||
}
|
||||
|
||||
const Delete = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
|
|
|
@ -89,7 +89,6 @@ const Layout = () => {
|
|||
|
||||
return (
|
||||
<Box sx={{display: 'flex'}}>
|
||||
<CssBaseline/>
|
||||
<ActionBar
|
||||
selected={selected}
|
||||
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
|
||||
|
|
|
@ -485,7 +485,7 @@ const Reservations = () => {
|
|||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
if (!session.exists() || !account || account.role === "admin") {
|
||||
if (!config.enable_reserve_topics || !session.exists() || !account || account.role === "admin") {
|
||||
return <></>;
|
||||
}
|
||||
const reservations = account.reservations || [];
|
||||
|
|
|
@ -76,7 +76,7 @@ const SubscribeDialog = (props) => {
|
|||
|
||||
const SubscribePage = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useOutletContext();
|
||||
//const { account } = useOutletContext();
|
||||
const [reserveTopicVisible, setReserveTopicVisible] = useState(false);
|
||||
const [anotherServerVisible, setAnotherServerVisible] = useState(false);
|
||||
const [errorText, setErrorText] = useState("");
|
||||
|
@ -87,7 +87,7 @@ const SubscribePage = (props) => {
|
|||
const existingBaseUrls = Array
|
||||
.from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)]))
|
||||
.filter(s => s !== config.base_url);
|
||||
const reserveTopicEnabled = session.exists() && (account?.stats.topics_remaining || 0) > 0;
|
||||
//const reserveTopicEnabled = session.exists() && (account?.stats.topics_remaining || 0) > 0;
|
||||
|
||||
const handleSubscribe = async () => {
|
||||
const user = await userManager.get(baseUrl); // May be undefined
|
||||
|
@ -177,14 +177,14 @@ const SubscribePage = (props) => {
|
|||
{t("subscribe_dialog_subscribe_button_generate_topic_name")}
|
||||
</Button>
|
||||
</div>
|
||||
{session.exists() && !anotherServerVisible &&
|
||||
{config.enable_reserve_topics && session.exists() && !anotherServerVisible &&
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
variant="standard"
|
||||
control={
|
||||
<Checkbox
|
||||
fullWidth
|
||||
disabled={account.stats.topics_remaining}
|
||||
// disabled={account.stats.topics_remaining}
|
||||
checked={reserveTopicVisible}
|
||||
onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
|
||||
inputProps={{
|
||||
|
|
|
@ -78,26 +78,30 @@ const SubscriptionSettingsDialog = (props) => {
|
|||
"aria-label": t("subscription_settings_dialog_display_name_placeholder")
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
fullWidth
|
||||
variant="standard"
|
||||
sx={{pt: 1}}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={reserveTopicVisible}
|
||||
onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
|
||||
inputProps={{
|
||||
"aria-label": t("subscription_settings_dialog_reserve_topic_label")
|
||||
}}
|
||||
{config.enable_reserve_topics && session.exists() &&
|
||||
<>
|
||||
<FormControlLabel
|
||||
fullWidth
|
||||
variant="standard"
|
||||
sx={{pt: 1}}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={reserveTopicVisible}
|
||||
onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
|
||||
inputProps={{
|
||||
"aria-label": t("subscription_settings_dialog_reserve_topic_label")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={t("subscription_settings_dialog_reserve_topic_label")}
|
||||
/>
|
||||
}
|
||||
label={t("subscription_settings_dialog_reserve_topic_label")}
|
||||
/>
|
||||
{reserveTopicVisible &&
|
||||
<ReserveTopicSelect
|
||||
value={everyone}
|
||||
onChange={setEveryone}
|
||||
/>
|
||||
{reserveTopicVisible &&
|
||||
<ReserveTopicSelect
|
||||
value={everyone}
|
||||
onChange={setEveryone}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue