Skip to content

Commit a59b363

Browse files
authored
feat: improved update ui (#1691)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Global awaitable confirmation modal for notification actions. * “Ignore this release” toggle that persists to the server when used. * New test pages and standalone test controls for the update modal and theme switching. * **Refactor** * Update modal rebuilt with a responsive layout, unified “Update Available” title, revised action logic, and centralized modal plumbing. * **Style** * OS Update highlight block, improved spacing, refreshed iconography, and tooltips clarifying actions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2fef10c commit a59b363

13 files changed

Lines changed: 966 additions & 84 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
77
This is the Unraid API monorepo containing multiple packages that provide API functionality for Unraid servers. It uses pnpm workspaces with the following structure:
88

99
- `/api` - Core NestJS API server with GraphQL
10-
- `/web` - Nuxt.js frontend application
10+
- `/web` - Vue 3 frontend application
1111
- `/unraid-ui` - Vue 3 component library
1212
- `/plugin` - Unraid plugin package (.plg)
1313
- `/packages` - Shared packages and API plugins
@@ -128,9 +128,6 @@ Enables GraphQL playground at `http://tower.local/graphql`
128128
- **Use Mocks Correctly**: Mocks should be used as nouns, not verbs.
129129

130130
#### Vue Component Testing
131-
132-
- This is a Nuxt.js app but we are testing with vitest outside of the Nuxt environment
133-
- Nuxt is currently set to auto import so some vue files may need compute or ref imported
134131
- Use pnpm when running terminal commands and stay within the web directory
135132
- Tests are located under `web/__test__`, run with `pnpm test`
136133
- Use `mount` from Vue Test Utils for component testing

unraid-ui/src/components/brand/BrandButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const classes = computed(() => {
5151
});
5252
5353
const needsBrandGradientBackground = computed(() => {
54-
return ['outline-solid', 'outline-primary'].includes(props.variant ?? '');
54+
return ['outline', 'outline-solid', 'outline-primary'].includes(props.variant ?? '');
5555
});
5656
5757
const isLink = computed(() => Boolean(props.href));

web/components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ declare module 'vue' {
3131
ChangelogModal: typeof import('./src/components/UpdateOs/ChangelogModal.vue')['default']
3232
CheckUpdateResponseModal: typeof import('./src/components/UpdateOs/CheckUpdateResponseModal.vue')['default']
3333
'ColorSwitcher.standalone': typeof import('./src/components/ColorSwitcher.standalone.vue')['default']
34+
ConfirmDialog: typeof import('./src/components/ConfirmDialog.vue')['default']
3435
'ConnectSettings.standalone': typeof import('./src/components/ConnectSettings/ConnectSettings.standalone.vue')['default']
3536
Console: typeof import('./src/components/Docker/Console.vue')['default']
3637
Detail: typeof import('./src/components/LayoutViews/Detail/Detail.vue')['default']
@@ -97,6 +98,8 @@ declare module 'vue' {
9798
SsoButtons: typeof import('./src/components/sso/SsoButtons.vue')['default']
9899
SsoProviderButton: typeof import('./src/components/sso/SsoProviderButton.vue')['default']
99100
Status: typeof import('./src/components/UpdateOs/Status.vue')['default']
101+
'TestThemeSwitcher.standalone': typeof import('./src/components/TestThemeSwitcher.standalone.vue')['default']
102+
'TestUpdateModal.standalone': typeof import('./src/components/UpdateOs/TestUpdateModal.standalone.vue')['default']
100103
'ThemeSwitcher.standalone': typeof import('./src/components/ThemeSwitcher.standalone.vue')['default']
101104
ThirdPartyDrivers: typeof import('./src/components/UpdateOs/ThirdPartyDrivers.vue')['default']
102105
Trial: typeof import('./src/components/UserProfile/Trial.vue')['default']

web/public/test-pages/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ <h3>OS Management <span class="badge new">NEW</span></h3>
179179
</div>
180180
<a href="/test-pages/os-management.html">Open →</a>
181181
</div>
182+
183+
<div class="page-item">
184+
<div>
185+
<h3>Update Modal Testing <span class="badge new">NEW</span></h3>
186+
<p>Test various update scenarios including expired licenses, renewals, and auth requirements</p>
187+
</div>
188+
<a href="/test-update-modal.html">Open →</a>
189+
</div>
182190
</div>
183191
</div>
184192

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script setup lang="ts">
2+
import {
3+
Button,
4+
DialogContent,
5+
DialogDescription,
6+
DialogFooter,
7+
DialogHeader,
8+
DialogRoot,
9+
DialogTitle,
10+
} from '@unraid/ui';
11+
12+
import { useConfirm } from '~/composables/useConfirm';
13+
14+
const { isOpen, state, handleConfirm, handleCancel } = useConfirm();
15+
</script>
16+
17+
<template>
18+
<DialogRoot :open="isOpen" @update:open="!$event && handleCancel()">
19+
<DialogContent>
20+
<DialogHeader v-if="state">
21+
<DialogTitle>{{ state.title }}</DialogTitle>
22+
<DialogDescription v-if="state.description">
23+
{{ state.description }}
24+
</DialogDescription>
25+
</DialogHeader>
26+
<DialogFooter v-if="state">
27+
<div class="flex w-full justify-between gap-3">
28+
<Button variant="outline" @click="handleCancel">
29+
{{ state.cancelText }}
30+
</Button>
31+
<Button :variant="state.confirmVariant" @click="handleConfirm">
32+
{{ state.confirmText }}
33+
</Button>
34+
</div>
35+
</DialogFooter>
36+
</DialogContent>
37+
</DialogRoot>
38+
</template>

web/src/components/Notifications/Sidebar.vue

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from '@unraid/ui';
2222
import { Settings } from 'lucide-vue-next';
2323
24+
import ConfirmDialog from '~/components/ConfirmDialog.vue';
2425
import {
2526
archiveAllNotifications,
2627
deleteArchivedNotifications,
@@ -37,10 +38,12 @@ import NotificationsList from '~/components/Notifications/List.vue';
3738
import { useTrackLatestSeenNotification } from '~/composables/api/use-notifications';
3839
import { useFragment } from '~/composables/gql';
3940
import { NotificationImportance as Importance, NotificationType } from '~/composables/gql/graphql';
41+
import { useConfirm } from '~/composables/useConfirm';
4042
4143
const { mutate: archiveAll, loading: loadingArchiveAll } = useMutation(archiveAllNotifications);
4244
const { mutate: deleteArchives, loading: loadingDeleteAll } = useMutation(deleteArchivedNotifications);
4345
const { mutate: recalculateOverview } = useMutation(resetOverview);
46+
const { confirm } = useConfirm();
4447
const importance = ref<Importance | undefined>(undefined);
4548
4649
const filterItems = [
@@ -52,17 +55,26 @@ const filterItems = [
5255
];
5356
5457
const confirmAndArchiveAll = async () => {
55-
if (confirm('This will archive all notifications on your Unraid server. Continue?')) {
58+
const confirmed = await confirm({
59+
title: 'Archive All Notifications',
60+
description: 'This will archive all notifications on your Unraid server. Continue?',
61+
confirmText: 'Archive All',
62+
confirmVariant: 'primary',
63+
});
64+
if (confirmed) {
5665
await archiveAll();
5766
}
5867
};
5968
6069
const confirmAndDeleteArchives = async () => {
61-
if (
62-
confirm(
63-
'This will permanently delete all archived notifications currently on your Unraid server. Continue?'
64-
)
65-
) {
70+
const confirmed = await confirm({
71+
title: 'Delete All Archived Notifications',
72+
description:
73+
'This will permanently delete all archived notifications currently on your Unraid server. This action cannot be undone.',
74+
confirmText: 'Delete All',
75+
confirmVariant: 'destructive',
76+
});
77+
if (confirmed) {
6678
await deleteArchives();
6779
}
6880
};
@@ -230,4 +242,7 @@ const prepareToViewNotifications = () => {
230242
</div>
231243
</SheetContent>
232244
</Sheet>
245+
246+
<!-- Global Confirm Dialog -->
247+
<ConfirmDialog />
233248
</template>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script lang="ts" setup>
2+
import { computed } from 'vue';
3+
import { storeToRefs } from 'pinia';
4+
5+
import { Select } from '@unraid/ui';
6+
7+
import type { Theme } from '~/themes/types';
8+
9+
import { useThemeStore } from '~/store/theme';
10+
11+
const themeStore = useThemeStore();
12+
const { theme } = storeToRefs(themeStore);
13+
14+
// Available theme options
15+
const items = [
16+
{ value: 'white', label: 'Light' },
17+
{ value: 'black', label: 'Dark' },
18+
{ value: 'azure', label: 'Azure' },
19+
{ value: 'gray', label: 'Gray' },
20+
];
21+
22+
// Current theme value
23+
const currentTheme = computed({
24+
get: () => theme.value.name,
25+
set: (value: string) => {
26+
const newTheme: Theme = {
27+
...theme.value,
28+
name: value,
29+
};
30+
themeStore.setTheme(newTheme);
31+
},
32+
});
33+
</script>
34+
35+
<template>
36+
<div class="flex items-center gap-2">
37+
<span class="text-sm font-medium text-white">Theme:</span>
38+
<Select v-model="currentTheme" :items="items" placeholder="Select theme" class="w-32" />
39+
</div>
40+
</template>

0 commit comments

Comments
 (0)