Skip to content

Commit 2c08bad

Browse files
committed
Allow admins to see which tutorials are available per role
1 parent bbc57e2 commit 2c08bad

File tree

1 file changed

+134
-33
lines changed

1 file changed

+134
-33
lines changed

frontend/src/routes/(root)/(logged)/tutorials/+page.svelte

Lines changed: 134 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import CenteredPage from '$lib/components/CenteredPage.svelte'
3-
import PageHeader from '$lib/components/PageHeader.svelte'
43
import { Tab } from '$lib/components/common'
4+
import Tooltip from '$lib/components/Tooltip.svelte'
55
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
66
import TutorialButton from '$lib/components/home/TutorialButton.svelte'
77
import TutorialProgressBar from '$lib/components/tutorials/TutorialProgressBar.svelte'
@@ -20,10 +20,16 @@
2020
completeTutorialByIndex
2121
} from '$lib/tutorialUtils'
2222
import { Button } from '$lib/components/common'
23-
import { RefreshCw, CheckCheck, CheckCircle2, Circle } from 'lucide-svelte'
23+
import { RefreshCw, CheckCheck, CheckCircle2, Circle, Shield, Code, UserCog } from 'lucide-svelte'
2424
import { TUTORIALS_CONFIG, type TabId } from '$lib/tutorials/config'
2525
import { userStore } from '$lib/stores'
2626
import type { UserExt } from '$lib/stores'
27+
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
28+
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
29+
30+
// Role override for admins to preview what other roles see
31+
// Only used when user is admin - defaults to 'admin' (their actual role)
32+
let roleOverride: 'admin' | 'developer' | 'operator' = $state('admin')
2733
2834
/**
2935
* Check if a user has access based on a roles array.
@@ -45,13 +51,49 @@
4551
})
4652
}
4753
54+
// Debug: Log user role for troubleshooting
55+
$effect(() => {
56+
const user = $userStore
57+
if (user) {
58+
console.log('Tutorials page - User role:', {
59+
is_admin: user.is_admin,
60+
operator: user.operator,
61+
effectiveRole: user.is_admin ? 'admin' : user.operator ? 'operator' : 'developer',
62+
roleOverride: user.is_admin ? roleOverride : 'N/A (not admin)'
63+
})
64+
}
65+
})
66+
67+
/**
68+
* Check if a preview role has access based on a roles array.
69+
* Used by admins to preview what other roles can see.
70+
*/
71+
function hasRoleAccessForPreview(
72+
previewRole: 'admin' | 'developer' | 'operator',
73+
roles?: ('admin' | 'developer' | 'operator')[]
74+
): boolean {
75+
// No roles specified = available to everyone
76+
if (!roles || roles.length === 0) return true
77+
78+
// Check if preview role has any of the required roles
79+
return roles.some((role) => {
80+
if (role === 'admin') return previewRole === 'admin'
81+
if (role === 'operator') return previewRole === 'operator' || previewRole === 'admin'
82+
if (role === 'developer') return previewRole === 'developer' || previewRole === 'admin'
83+
return false
84+
})
85+
}
86+
4887
// Get active tabs only (filtered by active and roles)
4988
const activeTabs = $derived.by(() => {
5089
const user = $userStore
5190
return Object.entries(TUTORIALS_CONFIG).filter(([, config]) => {
5291
// Filter by active
5392
if (config.active === false) return false
54-
// Filter by roles
93+
// Filter by roles - use preview function if admin has selected a role override
94+
if (user?.is_admin && roleOverride !== 'admin') {
95+
return hasRoleAccessForPreview(roleOverride, config.roles)
96+
}
5597
return hasRoleAccess(user, config.roles)
5698
}) as [TabId, typeof TUTORIALS_CONFIG[TabId]][]
5799
})
@@ -77,6 +119,10 @@
77119
const visibleTutorials = $derived(
78120
currentTabConfig.tutorials.filter((tutorial) => {
79121
if (tutorial.active === false) return false
122+
// Use preview function if admin has selected a role override
123+
if ($userStore?.is_admin && roleOverride !== 'admin') {
124+
return hasRoleAccessForPreview(roleOverride, tutorial.roles)
125+
}
80126
return hasRoleAccess($userStore, tutorial.roles)
81127
})
82128
)
@@ -198,7 +244,12 @@
198244
const indexes: number[] = []
199245
for (const tutorial of tabConfig.tutorials) {
200246
if (tutorial.active === false || tutorial.index === undefined) continue
201-
if (!hasRoleAccess(user, tutorial.roles)) continue
247+
// Use preview function if admin has selected a role override
248+
if (user?.is_admin && roleOverride !== 'admin') {
249+
if (!hasRoleAccessForPreview(roleOverride, tutorial.roles)) continue
250+
} else {
251+
if (!hasRoleAccess(user, tutorial.roles)) continue
252+
}
202253
indexes.push(tutorial.index)
203254
}
204255
@@ -228,38 +279,88 @@
228279
</script>
229280

230281
<CenteredPage>
231-
<PageHeader
232-
title="Tutorials"
233-
tooltip="Learn how to use Windmill with our interactive tutorials"
234-
documentationLink="https://www.windmill.dev/docs/intro"
235-
>
236-
{#if activeTabs.length > 0}
237-
<div class="flex gap-2">
238-
<Button
239-
size="xs"
240-
variant="default"
241-
startIcon={{ icon: CheckCheck }}
242-
onclick={async () => {
243-
await skipAllTodos()
244-
await syncTutorialsTodos()
245-
}}
246-
>
247-
Mark all as completed
248-
</Button>
249-
<Button
250-
size="xs"
251-
variant="default"
252-
startIcon={{ icon: RefreshCw }}
253-
onclick={async () => {
254-
await resetAllTodos()
255-
await syncTutorialsTodos()
256-
}}
282+
<div class="flex flex-col gap-4 pb-2 my-4 mr-2">
283+
<div class="flex flex-row flex-wrap justify-between items-start">
284+
<span class="flex items-center gap-2">
285+
<h1 class="text-2xl font-semibold text-emphasis whitespace-nowrap leading-6 tracking-tight"
286+
>Tutorials</h1
257287
>
258-
Reset all
259-
</Button>
288+
<Tooltip documentationLink="https://www.windmill.dev/docs/intro">
289+
Learn how to use Windmill with our interactive tutorials
290+
</Tooltip>
291+
</span>
292+
{#if activeTabs.length > 0}
293+
<div class="flex items-start gap-2 pt-1">
294+
<Button
295+
size="xs"
296+
variant="default"
297+
startIcon={{ icon: CheckCheck }}
298+
onclick={async () => {
299+
await skipAllTodos()
300+
await syncTutorialsTodos()
301+
}}
302+
>
303+
Mark all as completed
304+
</Button>
305+
<Button
306+
size="xs"
307+
variant="default"
308+
startIcon={{ icon: RefreshCw }}
309+
onclick={async () => {
310+
await resetAllTodos()
311+
await syncTutorialsTodos()
312+
}}
313+
>
314+
Reset all
315+
</Button>
316+
</div>
317+
{/if}
318+
</div>
319+
{#if $userStore?.is_admin}
320+
<div class="flex flex-col gap-1">
321+
<div class="flex items-center gap-2">
322+
<span class="text-xs text-secondary">View as an</span>
323+
<ToggleButtonGroup
324+
bind:selected={roleOverride}
325+
onSelected={(v) => {
326+
roleOverride = (v || 'admin') as typeof roleOverride
327+
}}
328+
noWFull
329+
>
330+
{#snippet children({ item })}
331+
<ToggleButton
332+
value="admin"
333+
label="Admin (me)"
334+
icon={Shield}
335+
size="sm"
336+
{item}
337+
tooltip="View tutorials as yourself (admin)"
338+
/>
339+
<ToggleButton
340+
value="developer"
341+
label="Developer"
342+
icon={Code}
343+
size="sm"
344+
{item}
345+
tooltip="Preview tutorials visible to developers"
346+
/>
347+
<ToggleButton
348+
value="operator"
349+
label="Operator"
350+
icon={UserCog}
351+
size="sm"
352+
{item}
353+
tooltip="Preview tutorials visible to operators"
354+
/>
355+
{/snippet}
356+
</ToggleButtonGroup>
357+
</div>
358+
<span class="text-3xs text-secondary">
359+
This allows you to see which tutorials your team members can access
360+
</span>
260361
</div>
261362
{/if}
262-
</PageHeader>
363+
</div>
263364

264365
{#if activeTabs.length > 0}
265366
<div class="flex justify-between pt-4">

0 commit comments

Comments
 (0)