|
1 | 1 | <script lang="ts"> |
2 | 2 | import CenteredPage from '$lib/components/CenteredPage.svelte' |
3 | | - import PageHeader from '$lib/components/PageHeader.svelte' |
4 | 3 | import { Tab } from '$lib/components/common' |
| 4 | + import Tooltip from '$lib/components/Tooltip.svelte' |
5 | 5 | import Tabs from '$lib/components/common/tabs/Tabs.svelte' |
6 | 6 | import TutorialButton from '$lib/components/home/TutorialButton.svelte' |
7 | 7 | import TutorialProgressBar from '$lib/components/tutorials/TutorialProgressBar.svelte' |
|
20 | 20 | completeTutorialByIndex |
21 | 21 | } from '$lib/tutorialUtils' |
22 | 22 | 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' |
24 | 24 | import { TUTORIALS_CONFIG, type TabId } from '$lib/tutorials/config' |
25 | 25 | import { userStore } from '$lib/stores' |
26 | 26 | 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') |
27 | 33 |
|
28 | 34 | /** |
29 | 35 | * Check if a user has access based on a roles array. |
|
45 | 51 | }) |
46 | 52 | } |
47 | 53 |
|
| 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 | +
|
48 | 87 | // Get active tabs only (filtered by active and roles) |
49 | 88 | const activeTabs = $derived.by(() => { |
50 | 89 | const user = $userStore |
51 | 90 | return Object.entries(TUTORIALS_CONFIG).filter(([, config]) => { |
52 | 91 | // Filter by active |
53 | 92 | 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 | + } |
55 | 97 | return hasRoleAccess(user, config.roles) |
56 | 98 | }) as [TabId, typeof TUTORIALS_CONFIG[TabId]][] |
57 | 99 | }) |
|
77 | 119 | const visibleTutorials = $derived( |
78 | 120 | currentTabConfig.tutorials.filter((tutorial) => { |
79 | 121 | 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 | + } |
80 | 126 | return hasRoleAccess($userStore, tutorial.roles) |
81 | 127 | }) |
82 | 128 | ) |
|
198 | 244 | const indexes: number[] = [] |
199 | 245 | for (const tutorial of tabConfig.tutorials) { |
200 | 246 | 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 | + } |
202 | 253 | indexes.push(tutorial.index) |
203 | 254 | } |
204 | 255 | |
|
228 | 279 | </script> |
229 | 280 |
|
230 | 281 | <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 |
257 | 287 | > |
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> |
260 | 361 | </div> |
261 | 362 | {/if} |
262 | | - </PageHeader> |
| 363 | + </div> |
263 | 364 |
|
264 | 365 | {#if activeTabs.length > 0} |
265 | 366 | <div class="flex justify-between pt-4"> |
|
0 commit comments