Add manage dashboards page

This commit is contained in:
Manuel
2023-07-30 22:20:20 +02:00
parent 93c10da760
commit f61d0f5f8d
7 changed files with 266 additions and 2 deletions

View File

@@ -27,6 +27,7 @@ import {
IconDashboard,
IconGitFork,
IconHome,
IconLayoutDashboard,
IconLogout,
IconMailForward,
IconQuestionMark,
@@ -75,6 +76,16 @@ export const MainLayout = ({ children }: MainLayoutProps) => {
component={Link}
href="/manage/"
/>
<NavLink
label="Boards"
icon={
<ThemeIcon size="md" variant="light" color="red">
<IconLayoutDashboard size="1rem" />
</ThemeIcon>
}
component={Link}
href="/manage/boards"
/>
<NavLink
label="Users"
icon={

View File

@@ -0,0 +1,56 @@
import { Button, Group, Stack, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { ContextModalProps, modals } from '@mantine/modals';
import { getStaticFallbackConfig } from '~/tools/config/getFallbackConfig';
import { api } from '~/utils/api';
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
import { createDashboardSchemaValidation } from '~/validations/dashboards';
export const CreateDashboardModal = ({ context, id, innerProps }: ContextModalProps<{}>) => {
const apiContext = api.useContext();
const { isLoading, mutateAsync } = api.config.save.useMutation({
onSuccess: async () => {
await apiContext.config.all.invalidate();
modals.close(id);
},
});
const { i18nZodResolver } = useI18nZodResolver();
const form = useForm({
initialValues: {
name: '',
},
validate: i18nZodResolver(createDashboardSchemaValidation),
});
return (
<Stack>
<Text>A name cannot be changed after a dashboard has been created.</Text>
<TextInput label="Name" withAsterisk {...form.getInputProps('name')} />
<Group grow>
<Button
onClick={() => {
modals.close(id);
}}
>
Cancel
</Button>
<Button
onClick={async () => {
const fallbackConfig = getStaticFallbackConfig(form.values.name);
await mutateAsync({
name: form.values.name,
config: fallbackConfig,
});
}}
disabled={isLoading}
>
Create
</Button>
</Group>
</Stack>
);
};

View File

@@ -9,6 +9,7 @@ import { CategoryEditModal } from '~/components/Dashboard/Wrappers/Category/Cate
import { DeleteUserModal } from './delete-user/delete-user.modal';
import { CreateRegistrationTokenModal } from './create-registration-token/create-registration-token.modal';
import { DeleteRegistrationTokenModal } from './delete-registration-token/delete-registration-token.modal';
import { CreateDashboardModal } from './create-dashboard/create-dashboard.modal';
export const modals = {
editApp: EditAppModal,
@@ -20,7 +21,8 @@ export const modals = {
changeIntegrationPositionModal: ChangeWidgetPositionModal,
deleteUserModal: DeleteUserModal,
createRegistrationTokenModal: CreateRegistrationTokenModal,
deleteRegistrationTokenModal: DeleteRegistrationTokenModal
deleteRegistrationTokenModal: DeleteRegistrationTokenModal,
createDashboardModal: CreateDashboardModal
};
declare module '@mantine/modals' {

View File

@@ -0,0 +1,129 @@
import {
ActionIcon,
Badge,
Button,
Card,
Flex,
Group,
LoadingOverlay,
Menu,
SimpleGrid,
Table,
Text,
Title,
} from '@mantine/core';
import { useListState } from '@mantine/hooks';
import { modals } from '@mantine/modals';
import { IconDotsVertical, IconPlus, IconTrash } from '@tabler/icons-react';
import Link from 'next/link';
import { MainLayout } from '~/components/layout/admin/main-admin.layout';
import { CommonHeader } from '~/components/layout/common-header';
import { sleep } from '~/tools/client/time';
import { api } from '~/utils/api';
const BoardsPage = () => {
const { data } = api.config.all.useQuery();
const context = api.useContext();
const { mutateAsync: deletionMutationAsync } = api.config.delete.useMutation({
onSettled: () => {
void context.config.all.invalidate();
},
});
const [deletingDashboards, { append, filter }] = useListState<string>([]);
return (
<MainLayout>
<CommonHeader>
<title>Boards Homarr</title>
</CommonHeader>
<Title mb="xl">Boards</Title>
<Flex justify="end" mb="md">
<Button
onClick={() => {
modals.openContextModal({
modal: 'createDashboardModal',
title: <Text>Create new dashboard</Text>,
innerProps: {},
});
}}
leftIcon={<IconPlus size="1rem" />}
variant="default"
>
Create new dashboard
</Button>
</Flex>
{data && (
<SimpleGrid
cols={3}
spacing="lg"
breakpoints={[
{ maxWidth: '62rem', cols: 2, spacing: 'lg' },
{ maxWidth: '48rem', cols: 1, spacing: 'lg' },
]}
>
{data.map((board, index) => (
<Card key={index} shadow="sm" padding="lg" radius="md" pos="relative" withBorder>
<LoadingOverlay visible={deletingDashboards.includes(board)} />
<Group position="apart" mb="xs">
<Text weight={500}>{board}</Text>
<Badge color="pink" variant="light">
Filesystem
</Badge>
</Group>
<Text size="sm" color="dimmed">
With Fjord Tours you can explore more of the magical fjord landscapes with tours and
activities on and around the fjords of Norway
</Text>
<Group mt="md">
<Button
component={Link}
style={{ flexGrow: 1 }}
variant="default"
color="blue"
radius="md"
href={`/board/${board}`}
>
View dashboard
</Button>
<Menu>
<Menu.Target>
<ActionIcon h={34} w={34} variant="default">
<IconDotsVertical size="1rem" />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
onClick={async () => {
append(board);
// give user feedback, that it's being deleted
await sleep(500);
deletionMutationAsync({
name: board,
}).finally(async () => {
await sleep(500);
filter((item, _) => item !== board);
});
}}
icon={<IconTrash size="1rem" />}
color="red"
>
Permanently delete
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Group>
</Card>
))}
</SimpleGrid>
)}
</MainLayout>
);
};
export default BoardsPage;

View File

@@ -1,3 +1,4 @@
import { ConfigType } from '~/types/config';
import defaultConfig from '../../../data/configs/default.json';
export const getFallbackConfig = (name?: string) => ({
@@ -6,3 +7,64 @@ export const getFallbackConfig = (name?: string) => ({
name: name ?? 'default',
},
});
export const getStaticFallbackConfig = (name: string): ConfigType => ({
schemaVersion: 1,
configProperties: {
name: name,
},
categories: [
{
id: '47af36c0-47c1-4e5b-bfc7-ad645ee6a33f',
position: 1,
name: 'Welcome to Homarr 🎉',
},
],
wrappers: [
{
id: 'default',
position: 0,
},
{
id: '47af36c0-47c1-4e5b-bfc7-ad645ee6a326',
position: 1,
},
],
apps: [],
widgets: [],
settings: {
common: {
searchEngine: {
type: 'google',
properties: {
enabled: true,
openInNewTab: true,
},
},
},
customization: {
layout: {
enabledLeftSidebar: false,
enabledRightSidebar: false,
enabledDocker: false,
enabledPing: false,
enabledSearchbar: true,
},
accessibility: {
disablePingPulse: false,
replacePingDotsWithIcons: false
},
pageTitle: 'Homarr ⭐️',
logoImageUrl: '/imgs/logo/logo.png',
faviconUrl: '/imgs/favicon/favicon-squared.png',
backgroundImageUrl: '',
customCss: '',
colors: {
primary: 'red',
secondary: 'yellow',
shade: 7,
},
appOpacity: 100,
},
},
});

View File

@@ -7,7 +7,6 @@ export interface SettingsType {
export interface CommonSettingsType {
searchEngine: SearchEngineCommonSettingsType;
defaultConfig: string;
}
export type SearchEngineCommonSettingsType =

View File

@@ -0,0 +1,5 @@
import { z } from 'zod';
export const createDashboardSchemaValidation = z.object({
name: z.string().min(2).max(25),
});