mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Tidy up admin
structure
This commit is contained in:
108
admin/src/features/scope/components/AddRule.tsx
Normal file
108
admin/src/features/scope/components/AddRule.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { Alert } from "@mui/lab";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { ScopeDocument, ScopeQuery, ScopeRule, useSetScopeMutation } from "lib/graphql/generated";
|
||||
|
||||
function AddRule(): JSX.Element {
|
||||
const [ruleType, setRuleType] = useState("url");
|
||||
const [expression, setExpression] = useState("");
|
||||
|
||||
const client = useApolloClient();
|
||||
const [setScope, { error, loading }] = useSetScopeMutation({
|
||||
onCompleted({ setScope }) {
|
||||
client.writeQuery({
|
||||
query: ScopeDocument,
|
||||
data: { scope: setScope },
|
||||
});
|
||||
setExpression("");
|
||||
},
|
||||
});
|
||||
|
||||
const handleTypeChange = (e: React.ChangeEvent, value: string) => {
|
||||
setRuleType(value);
|
||||
};
|
||||
const handleSubmit = (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
let scope: ScopeRule[] = [];
|
||||
|
||||
try {
|
||||
const data = client.readQuery<ScopeQuery>({
|
||||
query: ScopeDocument,
|
||||
});
|
||||
if (data) {
|
||||
scope = data.scope;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
setScope({
|
||||
variables: {
|
||||
scope: [...scope.map(({ url }) => ({ url })), { url: expression }],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error && (
|
||||
<Box mb={4}>
|
||||
<Alert severity="error">Error adding rule: {error.message}</Alert>
|
||||
</Box>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} autoComplete="off">
|
||||
<FormControl fullWidth>
|
||||
<FormLabel color="primary" component="legend">
|
||||
Rule Type
|
||||
</FormLabel>
|
||||
<RadioGroup row name="ruleType" value={ruleType} onChange={handleTypeChange}>
|
||||
<FormControlLabel value="url" control={<Radio />} label="URL" />
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
label="Expression"
|
||||
placeholder="^https:\/\/(.*)example.com(.*)"
|
||||
helperText="Regular expression to match on."
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
required
|
||||
value={expression}
|
||||
onChange={(e) => setExpression(e.target.value)}
|
||||
InputProps={{
|
||||
sx: { fontFamily: "'JetBrains Mono', monospace" },
|
||||
}}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
margin="normal"
|
||||
/>
|
||||
</FormControl>
|
||||
<Box my={2}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={22} /> : <AddIcon />}
|
||||
>
|
||||
Add rule
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddRule;
|
91
admin/src/features/scope/components/RuleListItem.tsx
Normal file
91
admin/src/features/scope/components/RuleListItem.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {
|
||||
Avatar,
|
||||
Chip,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
import { ScopeDocument, ScopeQuery, useSetScopeMutation } from "lib/graphql/generated";
|
||||
|
||||
type ScopeRule = ScopeQuery["scope"][number];
|
||||
|
||||
type RuleListItemProps = {
|
||||
scope: ScopeQuery["scope"];
|
||||
rule: ScopeRule;
|
||||
index: number;
|
||||
};
|
||||
|
||||
function RuleListItem({ scope, rule, index }: RuleListItemProps): JSX.Element {
|
||||
const client = useApolloClient();
|
||||
const [setScope, { loading }] = useSetScopeMutation({
|
||||
onCompleted({ setScope }) {
|
||||
client.writeQuery({
|
||||
query: ScopeDocument,
|
||||
data: { scope: setScope },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const clone = [...scope];
|
||||
clone.splice(index, 1);
|
||||
setScope({
|
||||
variables: {
|
||||
scope: clone.map(({ url }) => ({ url })),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<CodeIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<RuleListItemText rule={rule} />
|
||||
<ListItemSecondaryAction>
|
||||
<RuleTypeChip rule={rule} />
|
||||
<Tooltip title="Delete rule">
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
<IconButton onClick={() => handleDelete(index)} disabled={loading}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function RuleListItemText({ rule }: { rule: ScopeRule }): JSX.Element {
|
||||
let text: JSX.Element = <div></div>;
|
||||
|
||||
if (rule.url) {
|
||||
text = <code>{rule.url}</code>;
|
||||
}
|
||||
|
||||
// TODO: Parse and handle rule.header and rule.body.
|
||||
|
||||
return <ListItemText>{text}</ListItemText>;
|
||||
}
|
||||
|
||||
function RuleTypeChip({ rule }: { rule: ScopeRule }): JSX.Element {
|
||||
let label = "Unknown";
|
||||
|
||||
if (rule.url) {
|
||||
label = "URL";
|
||||
}
|
||||
|
||||
return <Chip label={label} variant="outlined" />;
|
||||
}
|
||||
|
||||
export default RuleListItem;
|
31
admin/src/features/scope/components/Rules.tsx
Normal file
31
admin/src/features/scope/components/Rules.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Alert } from "@mui/lab";
|
||||
import { CircularProgress, List } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
import RuleListItem from "./RuleListItem";
|
||||
|
||||
import { useScopeQuery } from "lib/graphql/generated";
|
||||
|
||||
function Rules(): JSX.Element {
|
||||
const { loading, error, data } = useScopeQuery();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loading && <CircularProgress />}
|
||||
{error && <Alert severity="error">Error fetching scope: {error.message}</Alert>}
|
||||
{data && data.scope.length > 0 && (
|
||||
<List
|
||||
sx={{
|
||||
bgcolor: "background.paper",
|
||||
}}
|
||||
>
|
||||
{data.scope.map((rule, index) => (
|
||||
<RuleListItem key={index} rule={rule} scope={data.scope} index={index} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Rules;
|
Reference in New Issue
Block a user