Startup queries, foreign keys

This commit is contained in:
binwiederhier 2023-01-05 15:20:44 -05:00
parent 3280c2c440
commit 60f1882bec
14 changed files with 148 additions and 69 deletions

View file

@ -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"]
};

View file

@ -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",

View file

@ -1,5 +1,3 @@
import routes from "../components/routes";
class Session {
store(username, token) {
localStorage.setItem("user", username);

View file

@ -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 (

View file

@ -89,7 +89,6 @@ const Layout = () => {
return (
<Box sx={{display: 'flex'}}>
<CssBaseline/>
<ActionBar
selected={selected}
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}

View file

@ -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 || [];

View file

@ -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={{

View file

@ -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>