forked from Archives/Athou_commafeed
replace complex eslint config with biome
This commit is contained in:
@@ -1,153 +1,153 @@
|
||||
import { Trans } from "@lingui/macro"
|
||||
import { ActionIcon, Box, Code, Container, Group, Table, Text, Title, useMantineTheme } from "@mantine/core"
|
||||
import { closeAllModals, openConfirmModal, openModal } from "@mantine/modals"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import { type UserModel } from "app/types"
|
||||
import { UserEdit } from "components/admin/UserEdit"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Loader } from "components/Loader"
|
||||
import { RelativeDate } from "components/RelativeDate"
|
||||
import { type ReactNode } from "react"
|
||||
import { useAsync, useAsyncCallback } from "react-async-hook"
|
||||
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"
|
||||
|
||||
function BooleanIcon({ value }: { value: boolean }) {
|
||||
return value ? <TbCheck size={18} /> : <TbX size={18} />
|
||||
}
|
||||
|
||||
export function AdminUsersPage() {
|
||||
const theme = useMantineTheme()
|
||||
const query = useAsync(async () => await client.admin.getAllUsers(), [])
|
||||
const users = query.result?.data.sort((a, b) => a.id - b.id)
|
||||
|
||||
const deleteUser = useAsyncCallback(client.admin.deleteUser, {
|
||||
onSuccess: () => {
|
||||
query.execute()
|
||||
closeAllModals()
|
||||
},
|
||||
})
|
||||
|
||||
const openUserEditModal = (title: ReactNode, user?: UserModel) => {
|
||||
openModal({
|
||||
title,
|
||||
children: (
|
||||
<UserEdit
|
||||
user={user}
|
||||
onCancel={closeAllModals}
|
||||
onSave={() => {
|
||||
query.execute()
|
||||
closeAllModals()
|
||||
}}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const openUserDeleteModal = (user: UserModel) => {
|
||||
const userName = user.name
|
||||
openConfirmModal({
|
||||
title: <Trans>Delete user</Trans>,
|
||||
children: (
|
||||
<Text size="sm">
|
||||
<Trans>
|
||||
Are you sure you want to delete user <Code>{userName}</Code> ?
|
||||
</Trans>
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => await deleteUser.execute({ id: user.id }),
|
||||
})
|
||||
}
|
||||
|
||||
if (!users) return <Loader />
|
||||
return (
|
||||
<Container>
|
||||
<Title order={3} mb="md">
|
||||
<Group>
|
||||
<Trans>Manage users</Trans>
|
||||
<ActionIcon color={theme.primaryColor} variant="subtle" onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
|
||||
<TbPlus size={20} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Title>
|
||||
|
||||
{deleteUser.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(deleteUser.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>
|
||||
<Trans>Id</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Name</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>E-mail</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Date created</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Last login date</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Admin</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Enabled</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Actions</Trans>
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{users.map(u => (
|
||||
<Table.Tr key={u.id}>
|
||||
<Table.Td>{u.id}</Table.Td>
|
||||
<Table.Td>{u.name}</Table.Td>
|
||||
<Table.Td>{u.email}</Table.Td>
|
||||
<Table.Td>
|
||||
<RelativeDate date={u.created} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RelativeDate date={u.lastLogin} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<BooleanIcon value={u.admin} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<BooleanIcon value={u.enabled} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group>
|
||||
<ActionIcon
|
||||
color={theme.primaryColor}
|
||||
variant="subtle"
|
||||
onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}
|
||||
>
|
||||
<TbPencil size={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
color={theme.primaryColor}
|
||||
variant="subtle"
|
||||
onClick={() => openUserDeleteModal(u)}
|
||||
loading={deleteUser.loading}
|
||||
>
|
||||
<TbTrash size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
import { Trans } from "@lingui/macro"
|
||||
import { ActionIcon, Box, Code, Container, Group, Table, Text, Title, useMantineTheme } from "@mantine/core"
|
||||
import { closeAllModals, openConfirmModal, openModal } from "@mantine/modals"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import type { UserModel } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Loader } from "components/Loader"
|
||||
import { RelativeDate } from "components/RelativeDate"
|
||||
import { UserEdit } from "components/admin/UserEdit"
|
||||
import type { ReactNode } from "react"
|
||||
import { useAsync, useAsyncCallback } from "react-async-hook"
|
||||
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"
|
||||
|
||||
function BooleanIcon({ value }: { value: boolean }) {
|
||||
return value ? <TbCheck size={18} /> : <TbX size={18} />
|
||||
}
|
||||
|
||||
export function AdminUsersPage() {
|
||||
const theme = useMantineTheme()
|
||||
const query = useAsync(async () => await client.admin.getAllUsers(), [])
|
||||
const users = query.result?.data.sort((a, b) => a.id - b.id)
|
||||
|
||||
const deleteUser = useAsyncCallback(client.admin.deleteUser, {
|
||||
onSuccess: () => {
|
||||
query.execute()
|
||||
closeAllModals()
|
||||
},
|
||||
})
|
||||
|
||||
const openUserEditModal = (title: ReactNode, user?: UserModel) => {
|
||||
openModal({
|
||||
title,
|
||||
children: (
|
||||
<UserEdit
|
||||
user={user}
|
||||
onCancel={closeAllModals}
|
||||
onSave={() => {
|
||||
query.execute()
|
||||
closeAllModals()
|
||||
}}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const openUserDeleteModal = (user: UserModel) => {
|
||||
const userName = user.name
|
||||
openConfirmModal({
|
||||
title: <Trans>Delete user</Trans>,
|
||||
children: (
|
||||
<Text size="sm">
|
||||
<Trans>
|
||||
Are you sure you want to delete user <Code>{userName}</Code> ?
|
||||
</Trans>
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => await deleteUser.execute({ id: user.id }),
|
||||
})
|
||||
}
|
||||
|
||||
if (!users) return <Loader />
|
||||
return (
|
||||
<Container>
|
||||
<Title order={3} mb="md">
|
||||
<Group>
|
||||
<Trans>Manage users</Trans>
|
||||
<ActionIcon color={theme.primaryColor} variant="subtle" onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
|
||||
<TbPlus size={20} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Title>
|
||||
|
||||
{deleteUser.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(deleteUser.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>
|
||||
<Trans>Id</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Name</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>E-mail</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Date created</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Last login date</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Admin</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Enabled</Trans>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Trans>Actions</Trans>
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{users.map(u => (
|
||||
<Table.Tr key={u.id}>
|
||||
<Table.Td>{u.id}</Table.Td>
|
||||
<Table.Td>{u.name}</Table.Td>
|
||||
<Table.Td>{u.email}</Table.Td>
|
||||
<Table.Td>
|
||||
<RelativeDate date={u.created} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RelativeDate date={u.lastLogin} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<BooleanIcon value={u.admin} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<BooleanIcon value={u.enabled} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group>
|
||||
<ActionIcon
|
||||
color={theme.primaryColor}
|
||||
variant="subtle"
|
||||
onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}
|
||||
>
|
||||
<TbPencil size={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
color={theme.primaryColor}
|
||||
variant="subtle"
|
||||
onClick={() => openUserDeleteModal(u)}
|
||||
loading={deleteUser.loading}
|
||||
>
|
||||
<TbTrash size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
import { Accordion, Box, Tabs } from "@mantine/core"
|
||||
import { client } from "app/client"
|
||||
import { Loader } from "components/Loader"
|
||||
import { Gauge } from "components/metrics/Gauge"
|
||||
import { Meter } from "components/metrics/Meter"
|
||||
import { MetricAccordionItem } from "components/metrics/MetricAccordionItem"
|
||||
import { Timer } from "components/metrics/Timer"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { TbChartAreaLine, TbClock } from "react-icons/tb"
|
||||
|
||||
const shownMeters: Record<string, string> = {
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.refill": "Feed queue refill rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshWorker.feedFetched": "Feed fetching rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
|
||||
"com.commafeed.backend.service.db.DatabaseCleaningService.entriesDeleted": "Entries deleted",
|
||||
}
|
||||
|
||||
const shownGauges: Record<string, string> = {
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.queue.size": "Queue size",
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.worker.active": "Feed Worker active",
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.updater.active": "Feed Updater active",
|
||||
"com.commafeed.frontend.ws.WebSocketSessions.users": "WebSocket users",
|
||||
"com.commafeed.frontend.ws.WebSocketSessions.sessions": "WebSocket sessions",
|
||||
}
|
||||
|
||||
export function MetricsPage() {
|
||||
const query = useAsync(async () => await client.admin.getMetrics(), [])
|
||||
|
||||
if (!query.result) return <Loader />
|
||||
const { meters, gauges, timers } = query.result.data
|
||||
return (
|
||||
<Tabs defaultValue="stats">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="stats" leftSection={<TbChartAreaLine size={14} />}>
|
||||
Stats
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="timers" leftSection={<TbClock size={14} />}>
|
||||
Timers
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="stats" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(shownMeters).map(m => (
|
||||
<MetricAccordionItem key={m} metricKey={m} name={shownMeters[m]} headerValue={meters[m].count}>
|
||||
<Meter meter={meters[m]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
<Box pt="xs">
|
||||
{Object.keys(shownGauges).map(g => (
|
||||
<Box key={g}>
|
||||
<span>{shownGauges[g]} </span>
|
||||
<Gauge gauge={gauges[g]} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="timers" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(timers).map(key => (
|
||||
<MetricAccordionItem key={key} metricKey={key} name={key} headerValue={timers[key].count}>
|
||||
<Timer timer={timers[key]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
import { Accordion, Box, Tabs } from "@mantine/core"
|
||||
import { client } from "app/client"
|
||||
import { Loader } from "components/Loader"
|
||||
import { Gauge } from "components/metrics/Gauge"
|
||||
import { Meter } from "components/metrics/Meter"
|
||||
import { MetricAccordionItem } from "components/metrics/MetricAccordionItem"
|
||||
import { Timer } from "components/metrics/Timer"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { TbChartAreaLine, TbClock } from "react-icons/tb"
|
||||
|
||||
const shownMeters: Record<string, string> = {
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.refill": "Feed queue refill rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshWorker.feedFetched": "Feed fetching rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
|
||||
"com.commafeed.backend.service.db.DatabaseCleaningService.entriesDeleted": "Entries deleted",
|
||||
}
|
||||
|
||||
const shownGauges: Record<string, string> = {
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.queue.size": "Queue size",
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.worker.active": "Feed Worker active",
|
||||
"com.commafeed.backend.feed.FeedRefreshEngine.updater.active": "Feed Updater active",
|
||||
"com.commafeed.frontend.ws.WebSocketSessions.users": "WebSocket users",
|
||||
"com.commafeed.frontend.ws.WebSocketSessions.sessions": "WebSocket sessions",
|
||||
}
|
||||
|
||||
export function MetricsPage() {
|
||||
const query = useAsync(async () => await client.admin.getMetrics(), [])
|
||||
|
||||
if (!query.result) return <Loader />
|
||||
const { meters, gauges, timers } = query.result.data
|
||||
return (
|
||||
<Tabs defaultValue="stats">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="stats" leftSection={<TbChartAreaLine size={14} />}>
|
||||
Stats
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="timers" leftSection={<TbClock size={14} />}>
|
||||
Timers
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="stats" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(shownMeters).map(m => (
|
||||
<MetricAccordionItem key={m} metricKey={m} name={shownMeters[m]} headerValue={meters[m].count}>
|
||||
<Meter meter={meters[m]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
<Box pt="xs">
|
||||
{Object.keys(shownGauges).map(g => (
|
||||
<Box key={g}>
|
||||
<span>{shownGauges[g]} </span>
|
||||
<Gauge gauge={gauges[g]} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="timers" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(timers).map(key => (
|
||||
<MetricAccordionItem key={key} metricKey={key} name={key} headerValue={timers[key].count}>
|
||||
<Timer timer={timers[key]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user