Skip to content

Commit 0a33604

Browse files
committed
WIP: Manage Project Page
1 parent 18a751a commit 0a33604

File tree

6 files changed

+370
-2
lines changed

6 files changed

+370
-2
lines changed

src/Exceptionless.Web/ClientApp/resources.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
### TODO
66

77
- Investigate loading data in - export function load({ url, fetch }) {}
8-
- https://svelte.dev/docs/svelte/class#The-class:-directive migrate from cn / cslx
8+
- <https://svelte.dev/docs/svelte/class#The-class:-directive> migrate from cn / cslx
9+
- IsBoolean on model gen.
910

1011
#### shadcn svelte upgrade
1112

src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import type { NewProject, UpdateProject, ViewProject } from '$features/projects/models';
12
import type { WebSocketMessageValue } from '$features/websockets/models';
23

34
import { accessToken } from '$features/auth/index.svelte';
45
import { type FetchClientResponse, type ProblemDetails, useFetchClient } from '@exceptionless/fetchclient';
56
import { createMutation, createQuery, QueryClient, useQueryClient } from '@tanstack/svelte-query';
67

7-
import type { NewProject, ViewProject } from './models';
88

99
export async function invalidateProjectQueries(queryClient: QueryClient, message: WebSocketMessageValue<'ProjectChanged'>) {
1010
const { id, organization_id } = message;
@@ -29,6 +29,7 @@ export const queryKeys = {
2929
ids: (ids: string[] | undefined) => [...queryKeys.type, ...(ids ?? [])] as const,
3030
organization: (id: string | undefined) => [...queryKeys.type, 'organization', id] as const,
3131
postPromotedTab: (id: string | undefined) => [...queryKeys.id(id), 'promote-tab'] as const,
32+
resetData: (id: string | undefined) => [...queryKeys.id(id), 'reset-data'] as const,
3233
type: ['Project'] as const
3334
};
3435

@@ -84,6 +85,18 @@ export interface PostPromotedTabRequest {
8485
id: string | undefined;
8586
};
8687
}
88+
export interface ResetDataRequest {
89+
route: {
90+
id: string;
91+
};
92+
}
93+
94+
export interface UpdateProjectRequest {
95+
route: {
96+
id: string;
97+
};
98+
}
99+
87100

88101
export function deleteProject(request: DeleteProjectRequest) {
89102
const queryClient = useQueryClient();
@@ -220,3 +233,35 @@ export function postPromotedTab(request: PostPromotedTabRequest) {
220233
}
221234
}));
222235
}
236+
237+
export function resetData(request: ResetDataRequest) {
238+
return createMutation<void, ProblemDetails, void>(() => ({
239+
enabled: () => !!accessToken.current && !!request.route.id,
240+
mutationFn: async () => {
241+
const client = useFetchClient();
242+
await client.post(`projects/${request.route.id}/reset-data`, undefined, {
243+
expectedStatusCodes: [202]
244+
});
245+
},
246+
mutationKey: queryKeys.resetData(request.route.id)
247+
}));
248+
}
249+
250+
export function updateProject(request: UpdateProjectRequest) {
251+
const queryClient = useQueryClient();
252+
253+
return createMutation<ViewProject, ProblemDetails, UpdateProject>(() => ({
254+
mutationFn: async (data: UpdateProject) => {
255+
const client = useFetchClient();
256+
const response = await client.patchJSON<ViewProject>(`projects/${request.route.id}`, data);
257+
return response.data!;
258+
},
259+
onError: () => {
260+
queryClient.invalidateQueries({ queryKey: queryKeys.id(request.route.id) });
261+
},
262+
onSuccess: (project: ViewProject) => {
263+
queryClient.setQueryData(queryKeys.id(request.route.id), project);
264+
}
265+
}));
266+
}
267+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!-- filepath: /Users/blake/Projects/Exceptionless/Exceptionless/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/confirm-project-delete-AlertDialog.svelte -->
2+
<script lang="ts">
3+
import * as AlertDialog from '$comp/ui/alert-dialog';
4+
import { buttonVariants } from '$comp/ui/button';
5+
6+
interface Props {
7+
name: string;
8+
open: boolean;
9+
reset: () => Promise<void>;
10+
}
11+
12+
let { name, open = $bindable(false), reset }: Props = $props();
13+
14+
async function onSubmit() {
15+
await reset();
16+
open = false;
17+
}
18+
</script>
19+
20+
<AlertDialog.Root bind:open>
21+
<AlertDialog.Content>
22+
<AlertDialog.Header>
23+
<AlertDialog.Title>Reset Project Data</AlertDialog.Title>
24+
<AlertDialog.Description>
25+
Are you sure you want to reset all project data for "{name}"? This action cannot be undone and will permanently erase all events, stacks, and
26+
associated data.
27+
</AlertDialog.Description>
28+
</AlertDialog.Header>
29+
<AlertDialog.Footer>
30+
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
31+
<AlertDialog.Action class={buttonVariants({ variant: 'destructive' })} onclick={onSubmit}>Reset Project Data</AlertDialog.Action>
32+
</AlertDialog.Footer>
33+
</AlertDialog.Content>
34+
</AlertDialog.Root>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
export { NewProject, ViewProject } from '$generated/api';
2+
3+
import { IsBoolean, IsString } from 'class-validator';
4+
5+
export class UpdateProject {
6+
@IsBoolean({ message: 'delete_bot_data_enabled is required.' }) delete_bot_data_enabled: boolean = true;
7+
@IsString({ message: 'name is required.' }) name!: string;
8+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts">
2+
import { goto } from '$app/navigation';
3+
import { page } from '$app/state';
4+
import * as Card from '$comp/ui/card';
5+
import { Separator } from '$comp/ui/separator';
6+
import { getProjectQuery } from '$features/projects/api.svelte';
7+
import * as SplitLayout from '$features/shared/components/layouts/split-layout';
8+
import { toast } from 'svelte-sonner';
9+
10+
import SidebarNav from '../../(components)/sidebar-nav.svelte';
11+
import { routes } from './routes.svelte';
12+
13+
let { children } = $props();
14+
15+
const projectId = page.params.projectId || '';
16+
const projectResponse = getProjectQuery({
17+
route: {
18+
get id() {
19+
return projectId;
20+
}
21+
}
22+
});
23+
24+
$effect(() => {
25+
if (projectResponse.isError) {
26+
toast.error(`The project "${projectId}" could not be found.`);
27+
goto('/next/project/list');
28+
}
29+
});
30+
</script>
31+
32+
<Card.Root>
33+
<Card.Header>
34+
<Card.Title class="text-2xl" level={2}>Settings</Card.Title>
35+
<Card.Description>Manage your project settings and integrations.</Card.Description>
36+
</Card.Header>
37+
<Separator class="mx-6 my-6 w-auto" />
38+
39+
<Card.Content>
40+
<SplitLayout.Root>
41+
<SplitLayout.Sidebar>
42+
<SidebarNav routes={routes()} />
43+
</SplitLayout.Sidebar>
44+
<SplitLayout.Content>
45+
{@render children()}
46+
</SplitLayout.Content>
47+
</SplitLayout.Root>
48+
</Card.Content>
49+
</Card.Root>

0 commit comments

Comments
 (0)