1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-06-05 04:33:14 +02:00

Add Dexie for persistence; user management with dexie; this is the way

This commit is contained in:
Philipp Heckel 2022-03-01 21:23:12 -05:00
parent 8036aa2942
commit 23d275acec
16 changed files with 285 additions and 494 deletions
web/src/components

View file

@ -1,6 +1,18 @@
import * as React from 'react';
import {useState} from 'react';
import {FormControl, Select, Stack, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material";
import {useEffect, useState} from 'react';
import {
CardActions,
CardContent,
FormControl,
Select,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
useMediaQuery
} from "@mui/material";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import repository from "../app/Repository";
@ -11,6 +23,15 @@ import IconButton from "@mui/material/IconButton";
import Container from "@mui/material/Container";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Card from "@mui/material/Card";
import Button from "@mui/material/Button";
import db from "../app/db";
import {useLiveQuery} from "dexie-react-hooks";
import theme from "./theme";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
const Preferences = (props) => {
return (
@ -26,7 +47,7 @@ const Preferences = (props) => {
const Notifications = (props) => {
return (
<Paper sx={{p: 3}}>
<Card sx={{p: 3}}>
<Typography variant="h5">
Notifications
</Typography>
@ -34,7 +55,7 @@ const Notifications = (props) => {
<MinPriority/>
<DeleteAfter/>
</PrefGroup>
</Paper>
</Card>
);
};
@ -66,7 +87,7 @@ const DeleteAfter = () => {
repository.setDeleteAfter(ev.target.value);
}
return (
<Pref title="Minimum priority">
<Pref title="Delete notifications">
<FormControl fullWidth variant="standard" sx={{ m: 1 }}>
<Select value={deleteAfter} onChange={handleChange}>
<MenuItem value={0}>Never</MenuItem>
@ -139,53 +160,191 @@ const DefaultServer = (props) => {
};
const Users = (props) => {
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
const users = useLiveQuery(() => db.users.toArray());
const handleAddClick = () => {
setDialogKey(prev => prev+1);
setDialogOpen(true);
};
const handleDialogCancel = () => {
setDialogOpen(false);
};
const handleDialogSubmit = async (user) => {
setDialogOpen(false);
try {
await db.users.add(user);
console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`);
} catch (e) {
console.log(`[Preferences] Error adding user.`, e);
}
};
return (
<Paper sx={{p: 3}}>
<Typography variant="h5">
Manage users
</Typography>
<Paragraph>
You may manage users for your protected topics here. Please note that since this is a client
application only, username and password are stored in the browser's local storage.
</Paragraph>
<UserTable/>
</Paper>
<Card sx={{p: 3}}>
<CardContent>
<Typography variant="h5">
Manage users
</Typography>
<Paragraph>
Add/remove users for your protected topics here. Please note that username and password are
stored in the browser's local storage.
</Paragraph>
{users?.length > 0 && <UserTable users={users}/>}
</CardContent>
<CardActions>
<Button onClick={handleAddClick}>Add user</Button>
<UserDialog
key={`userAddDialog${dialogKey}`}
open={dialogOpen}
user={null}
users={users}
onCancel={handleDialogCancel}
onSubmit={handleDialogSubmit}
/>
</CardActions>
</Card>
);
};
const UserTable = () => {
const users = repository.loadUsers();
const UserTable = (props) => {
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogUser, setDialogUser] = useState(null);
const handleEditClick = (user) => {
setDialogKey(prev => prev+1);
setDialogUser(user);
setDialogOpen(true);
};
const handleDialogCancel = () => {
setDialogOpen(false);
};
const handleDialogSubmit = async (user) => {
setDialogOpen(false);
try {
await db.users.put(user); // put() is an upsert
console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`);
} catch (e) {
console.log(`[Preferences] Error updating user.`, e);
}
};
const handleDeleteClick = async (user) => {
try {
await db.users.delete(user.baseUrl);
console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`);
} catch (e) {
console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e);
}
};
return (
<Table size="small">
<TableHead>
<TableRow>
<TableCell>User</TableCell>
<TableCell>Service URL</TableCell>
<TableCell/>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>User</TableCell>
<TableCell>Service URL</TableCell>
<TableCell/>
</TableRow>
</TableHead>
<TableBody>
{props.users?.map(user => (
<TableRow
key={user.baseUrl}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">{user.username}</TableCell>
<TableCell>{user.baseUrl}</TableCell>
<TableCell align="right">
<IconButton onClick={() => handleEditClick(user)}>
<EditIcon/>
</IconButton>
<IconButton onClick={() => handleDeleteClick(user)}>
<CloseIcon />
</IconButton>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user, i) => (
<TableRow
key={i}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">{user.username}</TableCell>
<TableCell>{user.baseUrl}</TableCell>
<TableCell align="right">
<IconButton>
<EditIcon/>
</IconButton>
<IconButton>
<CloseIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
))}
</TableBody>
<UserDialog
key={`userEditDialog${dialogKey}`}
open={dialogOpen}
user={dialogUser}
users={props.users}
onCancel={handleDialogCancel}
onSubmit={handleDialogSubmit}
/>
</Table>
);
}
};
const UserDialog = (props) => {
const [baseUrl, setBaseUrl] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const editMode = props.user !== null;
const addButtonEnabled = (() => {
if (editMode) {
return username.length > 0 && password.length > 0;
}
const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl);
return !baseUrlExists && username.length > 0 && password.length > 0;
})();
const handleSubmit = async () => {
props.onSubmit({
baseUrl: baseUrl,
username: username,
password: password
})
};
useEffect(() => {
if (editMode) {
setBaseUrl(props.user.baseUrl);
setUsername(props.user.username);
setPassword(props.user.password);
}
}, []);
return (
<Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
<DialogTitle>{editMode ? "Edit user" : "Add user"}</DialogTitle>
<DialogContent>
{!editMode && <TextField
autoFocus
margin="dense"
id="baseUrl"
label="Service URL, e.g. https://ntfy.sh"
value={baseUrl}
onChange={ev => setBaseUrl(ev.target.value)}
type="url"
fullWidth
variant="standard"
/>}
<TextField
autoFocus={editMode}
margin="dense"
id="username"
label="Username, e.g. phil"
value={username}
onChange={ev => setUsername(ev.target.value)}
type="text"
fullWidth
variant="standard"
/>
<TextField
margin="dense"
id="password"
label="Password"
type="password"
value={password}
onChange={ev => setPassword(ev.target.value)}
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={props.onCancel}>Cancel</Button>
<Button onClick={handleSubmit} disabled={!addButtonEnabled}>{editMode ? "Save" : "Add"}</Button>
</DialogActions>
</Dialog>
);
};
export default Preferences;