mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-26 00:03:59 +00:00
replace popup with melt popover in settings pages
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
import FolderEditor from '$lib/components/FolderEditor.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
|
||||
import { Button, Drawer, DrawerContent, Popup, Skeleton } from '$lib/components/common'
|
||||
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
import FolderInfo from '$lib/components/FolderInfo.svelte'
|
||||
import FolderUsageInfo from '$lib/components/FolderUsageInfo.svelte'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
@@ -78,139 +79,137 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.folders}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.folders}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Folders"
|
||||
tooltip="Folders allow to group items such as scripts/flows/resources/schedule together and to grant homogenous RBAC permissions to groups and individual users towards them."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<Popup
|
||||
let:close
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New folder</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col gap-2">
|
||||
<input
|
||||
class="mr-2"
|
||||
on:keyup={(e) => handleKeyUp(e, () => close(null))}
|
||||
placeholder="New folder name"
|
||||
bind:value={newFolderName}
|
||||
/>
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Folders"
|
||||
tooltip="Folders allow to group items such as scripts/flows/resources/schedule together and to grant homogenous RBAC permissions to groups and individual users towards them."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New folder</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<input
|
||||
class="mr-2"
|
||||
on:keyup={(e) => handleKeyUp(e, () => close())}
|
||||
placeholder="New folder name"
|
||||
bind:value={newFolderName}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newFolderName}
|
||||
on:click={() => {
|
||||
addFolder()
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div>
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newFolderName}
|
||||
on:click={() => {
|
||||
addFolder()
|
||||
close()
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="relative mb-20 pt-8">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head class="w-20">Scripts</Cell>
|
||||
<Cell head class="w-20">Flows</Cell>
|
||||
<Cell head class="w-20">Apps</Cell>
|
||||
<Cell head class="w-20">Schedules</Cell>
|
||||
<Cell head class="w-20">Variables</Cell>
|
||||
<Cell head class="w-20">Resources</Cell>
|
||||
<Cell head class="w-20">Participants</Cell>
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if folders === undefined}
|
||||
{#each new Array(4) as _}
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<Skeleton layout={[[2]]} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#if folders.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="text-tertiary mt-2">No folders yet, create one!</td>
|
||||
</tr>
|
||||
{/if}
|
||||
<div class="relative mb-20 pt-8">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head class="w-20">Scripts</Cell>
|
||||
<Cell head class="w-20">Flows</Cell>
|
||||
<Cell head class="w-20">Apps</Cell>
|
||||
<Cell head class="w-20">Schedules</Cell>
|
||||
<Cell head class="w-20">Variables</Cell>
|
||||
<Cell head class="w-20">Resources</Cell>
|
||||
<Cell head class="w-20">Participants</Cell>
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if folders === undefined}
|
||||
{#each new Array(4) as _}
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<Skeleton layout={[[2]]} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#if folders.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="text-tertiary mt-2">No folders yet, create one!</td>
|
||||
</tr>
|
||||
{/if}
|
||||
|
||||
{#each folders as { name, extra_perms, owners, canWrite, summary } (name)}
|
||||
<Row
|
||||
hoverable
|
||||
on:click={() => {
|
||||
editFolderName = name
|
||||
folderDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Cell first>
|
||||
<span class="text-blue-500">{name}</span>
|
||||
{#if summary}
|
||||
<br />
|
||||
<span class="text-gray-500">{summary}</span>
|
||||
{/if}
|
||||
</Cell>
|
||||
<FolderUsageInfo {name} tabular />
|
||||
{#each folders as { name, extra_perms, owners, canWrite, summary } (name)}
|
||||
<Row
|
||||
hoverable
|
||||
on:click={() => {
|
||||
editFolderName = name
|
||||
folderDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Cell first>
|
||||
<span class="text-blue-500">{name}</span>
|
||||
{#if summary}
|
||||
<br />
|
||||
<span class="text-gray-500">{summary}</span>
|
||||
{/if}
|
||||
</Cell>
|
||||
<FolderUsageInfo {name} tabular />
|
||||
|
||||
<Cell><FolderInfo members={computeMembers(owners, extra_perms)} /></Cell>
|
||||
<Cell shouldStopPropagation>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: 'Manage folder',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
editFolderName = name
|
||||
folderDrawer.openDrawer()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: `Delete${canWrite ? '' : ' (require owner permissions)'}`,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
try {
|
||||
await FolderService.deleteFolder({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name
|
||||
})
|
||||
} catch (e) {
|
||||
sendUserToast(e.body, true)
|
||||
loadFolders()
|
||||
<Cell><FolderInfo members={computeMembers(owners, extra_perms)} /></Cell>
|
||||
<Cell shouldStopPropagation>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: 'Manage folder',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
editFolderName = name
|
||||
folderDrawer.openDrawer()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: `Delete${canWrite ? '' : ' (require owner permissions)'}`,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
try {
|
||||
await FolderService.deleteFolder({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name
|
||||
})
|
||||
} catch (e) {
|
||||
sendUserToast(e.body, true)
|
||||
loadFolders()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
</CenteredPage>
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
</CenteredPage>
|
||||
{/if}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import { GroupService } from '$lib/gen'
|
||||
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Button, Drawer, DrawerContent, Popup, Skeleton } from '$lib/components/common'
|
||||
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import GroupEditor from '$lib/components/GroupEditor.svelte'
|
||||
import GroupInfo from '$lib/components/GroupInfo.svelte'
|
||||
@@ -40,11 +41,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
function handleKeyUp(event: KeyboardEvent, close: () => void) {
|
||||
const key = event.key
|
||||
if (key === 'Enter') {
|
||||
event.preventDefault()
|
||||
addGroup()
|
||||
close()
|
||||
}
|
||||
}
|
||||
async function addGroup() {
|
||||
@@ -73,160 +75,162 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.groups}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Groups"
|
||||
tooltip="Group users together to grant roles and homegenous permissions. Same users can be in many groups at the same time."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div>
|
||||
<Popup
|
||||
let:close
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New group</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex-col flex gap-2">
|
||||
<input
|
||||
class="mr-2"
|
||||
on:keyup={handleKeyUp}
|
||||
placeholder="New group name"
|
||||
bind:value={newGroupName}
|
||||
/>
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newGroupName}
|
||||
on:click={() => {
|
||||
addGroup()
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="relative mb-20 pt-8">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head>Members</Cell>
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if groups === undefined}
|
||||
{#each new Array(4) as _}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<Skeleton layout={[[2]]} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each groups as { name, summary, extra_perms, canWrite } (name)}
|
||||
<Row
|
||||
hoverable
|
||||
on:click={() => {
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Cell first>
|
||||
<div class="flex flex-row gap-2 justify-between">
|
||||
<div>
|
||||
<span class="text-blue-500">{name}</span>
|
||||
{#if summary}
|
||||
<br />
|
||||
<span class="text-gray-500">{summary}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<GroupInfo {name} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: 'Manage group',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
await GroupService.deleteGroup({ workspace: $workspaceStore ?? '', name })
|
||||
loadGroups()
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.groups}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
|
||||
{#if instanceGroups && instanceGroups.length > 0}
|
||||
{:else}
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Instance Groups"
|
||||
tooltip="Instance Groups are managed by SCIM and are groups shared by every workspaces"
|
||||
documentationLink="https://www.windmill.dev/docs/misc/saml_and_scim#scim"
|
||||
/>
|
||||
<div class="relative mb-20 pt-8">
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>Name</th>
|
||||
<th>Members</th>
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each instanceGroups as { name, emails }}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="#{name}"
|
||||
title="Groups"
|
||||
tooltip="Group users together to grant roles and homegenous permissions. Same users can be in many groups at the same time."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div>
|
||||
<Popover
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New group</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
<div class="flex-col flex gap-2 p-4">
|
||||
<input
|
||||
class="mr-2"
|
||||
on:keyup={(e) => handleKeyUp(e, close)}
|
||||
placeholder="New group name"
|
||||
bind:value={newGroupName}
|
||||
/>
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newGroupName}
|
||||
on:click={() => {
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
addGroup()
|
||||
close()
|
||||
}}
|
||||
>{name}
|
||||
</a>
|
||||
</td>
|
||||
<td>{emails?.length ?? 0} members</td>
|
||||
</tr>
|
||||
{/each}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="relative mb-20 pt-8">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head>Members</Cell>
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if groups === undefined}
|
||||
{#each new Array(4) as _}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<Skeleton layout={[[2]]} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each groups as { name, summary, extra_perms, canWrite } (name)}
|
||||
<Row
|
||||
hoverable
|
||||
on:click={() => {
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Cell first>
|
||||
<div class="flex flex-row gap-2 justify-between">
|
||||
<div>
|
||||
<span class="text-blue-500">{name}</span>
|
||||
{#if summary}
|
||||
<br />
|
||||
<span class="text-gray-500">{summary}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<GroupInfo {name} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: 'Manage group',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: (e) => {
|
||||
e?.stopPropagation()
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
await GroupService.deleteGroup({ workspace: $workspaceStore ?? '', name })
|
||||
loadGroups()
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</DataTable>
|
||||
</div>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
|
||||
{#if instanceGroups && instanceGroups.length > 0}
|
||||
<PageHeader
|
||||
title="Instance Groups"
|
||||
tooltip="Instance Groups are managed by SCIM and are groups shared by every workspaces"
|
||||
documentationLink="https://www.windmill.dev/docs/misc/saml_and_scim#scim"
|
||||
/>
|
||||
<div class="relative mb-20 pt-8">
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>Name</th>
|
||||
<th>Members</th>
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each instanceGroups as { name, emails }}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="#{name}"
|
||||
on:click={() => {
|
||||
editGroupName = name
|
||||
groupDrawer.openDrawer()
|
||||
}}
|
||||
>{name}
|
||||
</a>
|
||||
</td>
|
||||
<td>{emails?.length ?? 0} members</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</div>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { GroupService } from '$lib/gen'
|
||||
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Button, Drawer, DrawerContent, Popup } from '$lib/components/common'
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
import InstanceGroupEditor from '$lib/components/InstanceGroupEditor.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
@@ -48,29 +49,30 @@
|
||||
<PageHeader title="Instance Groups">
|
||||
<div class="flex flex-row">
|
||||
<div>
|
||||
<Popup
|
||||
let:close
|
||||
<Popover
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New group</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex-col flex gap-2">
|
||||
<input class="mr-2" placeholder="New instance group name" bind:value={newGroupName} />
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newGroupName}
|
||||
on:click={() => {
|
||||
addInstanceGroup()
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</Popup>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
<div class="flex-col flex gap-2 p-4">
|
||||
<input class="mr-2" placeholder="New instance group name" bind:value={newGroupName} />
|
||||
<Button
|
||||
size="md"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newGroupName}
|
||||
on:click={() => {
|
||||
addInstanceGroup()
|
||||
close()
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
@@ -59,22 +59,14 @@
|
||||
import HighlightCode from '$lib/components/HighlightCode.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
import {
|
||||
ActionRow,
|
||||
Button,
|
||||
Popup,
|
||||
Skeleton,
|
||||
Tab,
|
||||
Alert,
|
||||
DrawerContent
|
||||
} from '$lib/components/common'
|
||||
import { ActionRow, Button, Skeleton, Tab, Alert, DrawerContent } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
import FlowMetadata from '$lib/components/FlowMetadata.svelte'
|
||||
import JobArgs from '$lib/components/JobArgs.svelte'
|
||||
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
|
||||
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { goto } from '$lib/navigation'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { forLater } from '$lib/forLater'
|
||||
@@ -89,7 +81,7 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import WorkflowTimeline from '$lib/components/WorkflowTimeline.svelte'
|
||||
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
|
||||
import HighlightTheme from '$lib/components/HighlightTheme.svelte'
|
||||
import PreprocessedArgsDisplay from '$lib/components/runs/PreprocessedArgsDisplay.svelte'
|
||||
import ExecutionDuration from '$lib/components/ExecutionDuration.svelte'
|
||||
@@ -589,8 +581,8 @@
|
||||
</Badge>
|
||||
</Button>
|
||||
{:else}
|
||||
<Popup floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
|
||||
<svelte:fragment slot="button">
|
||||
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button
|
||||
title={`Re-start this flow from step ${selectedJobStep} (included). ${
|
||||
!$enterpriseLicense
|
||||
@@ -609,46 +601,48 @@
|
||||
</Badge>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-secondary"
|
||||
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
|
||||
>
|
||||
<div class="flex w-full">
|
||||
{#if selectedJobStepType === 'forloop'}
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
bind:value={branchOrIterationN}
|
||||
class="!w-32 grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
/>
|
||||
{:else}
|
||||
<select
|
||||
bind:value={branchOrIterationN}
|
||||
class="!w-32 grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
>
|
||||
{#each restartBranchNames as [branchIdx, branchName]}
|
||||
<option value={branchIdx}>{branchName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Restart flow"
|
||||
on:click|once={() => {
|
||||
restartFlow(job?.id, selectedJobStep, branchOrIterationN)
|
||||
}}
|
||||
<svelte:fragment slot="content">
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-secondary"
|
||||
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
</label>
|
||||
</Popup>
|
||||
<div class="flex w-full">
|
||||
{#if selectedJobStepType === 'forloop'}
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
bind:value={branchOrIterationN}
|
||||
class="!w-32 grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
/>
|
||||
{:else}
|
||||
<select
|
||||
bind:value={branchOrIterationN}
|
||||
class="!w-32 grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
>
|
||||
{#each restartBranchNames as [branchIdx, branchName]}
|
||||
<option value={branchIdx}>{branchName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Restart flow"
|
||||
on:click|once={() => {
|
||||
restartFlow(job?.id, selectedJobStep, branchOrIterationN)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
</label>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if job?.job_kind === 'script' || job?.job_kind === 'flow'}
|
||||
@@ -755,14 +749,18 @@
|
||||
<Badge color="indigo">Tag: {job.tag}</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !job.visible_to_owner}
|
||||
<div
|
||||
><Badge color="red"
|
||||
>only visible to you <Tooltip
|
||||
>The option to hide this run from the owner of this script or flow was activated</Tooltip
|
||||
>
|
||||
</Badge></div
|
||||
>
|
||||
{#if !job.visible_to_owner || true}
|
||||
<div>
|
||||
<Badge color="red">
|
||||
only visible to you
|
||||
<Tooltip useDefaultIcon>
|
||||
<svelte:fragment slot="text">
|
||||
The option to hide this run from the owner of this script or flow was
|
||||
activated
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{#if job?.['labels'] && Array.isArray(job?.['labels']) && job?.['labels'].length > 0}
|
||||
{#each job?.['labels'] as label}
|
||||
@@ -773,7 +771,7 @@
|
||||
{/if}
|
||||
{#if concurrencyKey}
|
||||
<div>
|
||||
<Popover notClickable>
|
||||
<Tooltip notClickable>
|
||||
<svelte:fragment slot="text">
|
||||
This job has concurrency limits enabled with the key
|
||||
<a
|
||||
@@ -787,7 +785,7 @@
|
||||
>
|
||||
<Badge>Concurrency: {truncateRev(concurrencyKey, 20)}</Badge></a
|
||||
>
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import AssignableTags from '$lib/components/AssignableTags.svelte'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Button, Popup, Skeleton, Tab, Tabs } from '$lib/components/common'
|
||||
import { Button, Skeleton, Tab, Tabs } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
@@ -15,7 +16,13 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import WorkspaceGroup from '$lib/components/WorkerGroup.svelte'
|
||||
import { WorkerService, type WorkerPing, ConfigService, SettingService } from '$lib/gen'
|
||||
import { enterpriseLicense, superadmin, userStore, workspaceStore, userWorkspaces } from '$lib/stores'
|
||||
import {
|
||||
enterpriseLicense,
|
||||
superadmin,
|
||||
userStore,
|
||||
workspaceStore,
|
||||
userWorkspaces
|
||||
} from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { displayDate, groupBy, pluralize, truncate } from '$lib/utils'
|
||||
import { AlertTriangle, LineChart, List, Plus, Search } from 'lucide-svelte'
|
||||
@@ -272,167 +279,173 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.workers}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.workers}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Workers"
|
||||
tooltip="The workers are the dutiful servants that execute the jobs."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>
|
||||
{#if $superadmin}
|
||||
<div class="flex flex-row-reverse w-full pb-2 items-center gap-4">
|
||||
<div>
|
||||
<AssignableTags
|
||||
showWorkspaceRestriction
|
||||
on:refresh={() => {
|
||||
loadCustomTags()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<DefaultTags bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{
|
||||
icon: LineChart
|
||||
}}
|
||||
on:click={() => {
|
||||
queueMetricsDrawer?.openDrawer()
|
||||
}}
|
||||
>
|
||||
Queue metrics
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{
|
||||
icon: List
|
||||
}}
|
||||
on:click={() => {
|
||||
openSearchWithPrefilledText('!')
|
||||
}}
|
||||
>
|
||||
Service logs
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</PageHeader>
|
||||
|
||||
{#if workers != undefined}
|
||||
{#if groupedWorkers.length == 0}
|
||||
<p>No workers seem to be available</p>
|
||||
{/if}
|
||||
|
||||
<div class="pt-4 pb-8 w-full flex justify-between items-center"
|
||||
><h4
|
||||
>{groupWorkers?.length} Worker Groups <Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>Worker groups are groups of workers that share a config and are meant to be identical.
|
||||
Worker groups are meant to be used with tags. Tags can be assigned to scripts and flows
|
||||
and can be seen as dedicated queues. Only the corresponding
|
||||
</Tooltip></h4
|
||||
>
|
||||
<div />
|
||||
|
||||
{#if $superadmin}
|
||||
<div class="flex flex-row items-center">
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
nonCaptureEvent
|
||||
disabled={!$enterpriseLicense}
|
||||
dropdownItems={$enterpriseLicense
|
||||
? [
|
||||
{
|
||||
label: 'Copy groups config as YAML',
|
||||
onClick: () => {
|
||||
if (!workerGroups) {
|
||||
return sendUserToast('No worker groups found', true)
|
||||
}
|
||||
|
||||
const workersConfig = Object.entries(workerGroups).map(
|
||||
([name, config]) => ({
|
||||
name,
|
||||
...config
|
||||
})
|
||||
)
|
||||
navigator.clipboard.writeText(YAML.stringify(workersConfig))
|
||||
sendUserToast('Worker groups config copied to clipboard as YAML')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Import groups config from YAML',
|
||||
onClick: () => {
|
||||
importConfigDrawer?.toggleDrawer?.()
|
||||
}
|
||||
}
|
||||
]
|
||||
: undefined}
|
||||
>
|
||||
<span class="hidden md:block"
|
||||
>New group config {!$enterpriseLicense ? '(EE)' : ''}</span
|
||||
>
|
||||
|
||||
<Tooltip light>
|
||||
Worker Group configs are propagated to every workers in the worker group
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col gap-2">
|
||||
<input class="mr-2 h-full" placeholder="New group name" bind:value={newConfigName} />
|
||||
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="flex items-center whitespace-nowrap text-yellow-600 gap-2">
|
||||
<AlertTriangle size={16} />
|
||||
EE only
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newConfigName || !$enterpriseLicense}
|
||||
on:click={addConfig}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
{/if}</div
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Workers"
|
||||
tooltip="The workers are the dutiful servants that execute the jobs."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>
|
||||
{#if $superadmin}
|
||||
<div class="flex flex-row-reverse w-full pb-2 items-center gap-4">
|
||||
<div>
|
||||
<AssignableTags
|
||||
showWorkspaceRestriction
|
||||
on:refresh={() => {
|
||||
loadCustomTags()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<DefaultTags bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{
|
||||
icon: LineChart
|
||||
}}
|
||||
on:click={() => {
|
||||
queueMetricsDrawer?.openDrawer()
|
||||
}}
|
||||
>
|
||||
Queue metrics
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{
|
||||
icon: List
|
||||
}}
|
||||
on:click={() => {
|
||||
openSearchWithPrefilledText('!')
|
||||
}}
|
||||
>
|
||||
Service logs
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</PageHeader>
|
||||
|
||||
{#if (groupedWorkers ?? []).length > 5}
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="text-secondary text-sm">Worker group:</div>
|
||||
<AutoComplete
|
||||
noInputStyles
|
||||
items={groupedWorkers.map((x) => x[0])}
|
||||
bind:selectedItem={selectedTab}
|
||||
hideArrow={true}
|
||||
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary"'}
|
||||
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
|
||||
className="!font-gray-600 !font-primary !bg-surface-primary"
|
||||
/>
|
||||
{#if workers != undefined}
|
||||
{#if groupedWorkers.length == 0}
|
||||
<p>No workers seem to be available</p>
|
||||
{/if}
|
||||
|
||||
<!-- <select
|
||||
<div class="pt-4 pb-8 w-full flex justify-between items-center"
|
||||
><h4
|
||||
>{groupWorkers?.length} Worker Groups <Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>Worker groups are groups of workers that share a config and are meant to be identical.
|
||||
Worker groups are meant to be used with tags. Tags can be assigned to scripts and flows
|
||||
and can be seen as dedicated queues. Only the corresponding
|
||||
</Tooltip></h4
|
||||
>
|
||||
<div />
|
||||
|
||||
{#if $superadmin}
|
||||
<div class="flex flex-row items-center">
|
||||
<Popover
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
nonCaptureEvent
|
||||
disabled={!$enterpriseLicense}
|
||||
dropdownItems={$enterpriseLicense
|
||||
? [
|
||||
{
|
||||
label: 'Copy groups config as YAML',
|
||||
onClick: () => {
|
||||
if (!workerGroups) {
|
||||
return sendUserToast('No worker groups found', true)
|
||||
}
|
||||
|
||||
const workersConfig = Object.entries(workerGroups).map(
|
||||
([name, config]) => ({
|
||||
name,
|
||||
...config
|
||||
})
|
||||
)
|
||||
navigator.clipboard.writeText(YAML.stringify(workersConfig))
|
||||
sendUserToast('Worker groups config copied to clipboard as YAML')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Import groups config from YAML',
|
||||
onClick: () => {
|
||||
importConfigDrawer?.toggleDrawer?.()
|
||||
}
|
||||
}
|
||||
]
|
||||
: undefined}
|
||||
>
|
||||
<span class="hidden md:block"
|
||||
>New group config {!$enterpriseLicense ? '(EE)' : ''}</span
|
||||
>
|
||||
|
||||
<Tooltip light>
|
||||
Worker Group configs are propagated to every workers in the worker group
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<input
|
||||
class="mr-2 h-full"
|
||||
placeholder="New group name"
|
||||
bind:value={newConfigName}
|
||||
/>
|
||||
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="flex items-center whitespace-nowrap text-yellow-600 gap-2">
|
||||
<AlertTriangle size={16} />
|
||||
EE only
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
disabled={!newConfigName || !$enterpriseLicense}
|
||||
on:click={addConfig}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}</div
|
||||
>
|
||||
|
||||
{#if (groupedWorkers ?? []).length > 5}
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="text-secondary text-sm">Worker group:</div>
|
||||
<AutoComplete
|
||||
noInputStyles
|
||||
items={groupedWorkers.map((x) => x[0])}
|
||||
bind:selectedItem={selectedTab}
|
||||
hideArrow={true}
|
||||
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary"'}
|
||||
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
|
||||
className="!font-gray-600 !font-primary !bg-surface-primary"
|
||||
/>
|
||||
|
||||
<!-- <select
|
||||
class="max-w-64"
|
||||
bind:value={selectedTab}
|
||||
on:change={() => {
|
||||
@@ -448,221 +461,226 @@
|
||||
</option>
|
||||
{/each}
|
||||
</select> -->
|
||||
</div>
|
||||
{:else}
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
{#each groupedWorkers.map((x) => x[0]) as name (name)}
|
||||
{@const worker_group = groupedWorkers.find((x) => x[0] == name)}
|
||||
|
||||
{#if worker_group}
|
||||
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
|
||||
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
|
||||
)}
|
||||
<Tab value={worker_group[0]}>
|
||||
{`${worker_group[0]} - ${pluralize(activeWorkers?.length, 'worker')}`}
|
||||
<Tooltip>Number of workers active in the last 15s</Tooltip>
|
||||
</Tab>
|
||||
{:else}
|
||||
<Tab value={name}>
|
||||
{name} (0 worker)
|
||||
</Tab>
|
||||
{/if}
|
||||
{/each}
|
||||
</Tabs>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
{#if worker_group}
|
||||
{@const config = (workerGroups ?? {})[worker_group[0]]}
|
||||
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
|
||||
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
|
||||
)}
|
||||
<WorkspaceGroup
|
||||
{customTags}
|
||||
name={worker_group[0]}
|
||||
workers={worker_group[1]}
|
||||
{config}
|
||||
on:reload={() => {
|
||||
loadWorkerGroups()
|
||||
}}
|
||||
activeWorkers={activeWorkers?.length ?? 0}
|
||||
{defaultTagPerWorkspace}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row items-center gap-2 relative my-2">
|
||||
<input
|
||||
class="max-w-80 border rounded-md !pl-8"
|
||||
placeholder="Search workers by name..."
|
||||
autocomplete="off"
|
||||
bind:value={search}
|
||||
/>
|
||||
<Search class="absolute left-2 " size={14} />
|
||||
</div>
|
||||
{#if worker_group?.[1].length == 0 && search}
|
||||
<div class="text-xs text-tertiary">
|
||||
No workers found. Reset the search to see all workers.
|
||||
</div>
|
||||
{:else}
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Worker</Cell>
|
||||
<Cell head>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
Worker Tags
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups#assign-custom-worker-groups"
|
||||
>
|
||||
If defined, the workers only pull jobs with the same corresponding tag
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell head>Last ping</Cell>
|
||||
<Cell head>Worker start</Cell>
|
||||
<Cell head>Jobs ran</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
<Cell head>Last job</Cell>
|
||||
<Cell head>Occupancy rate<br />(15s/5m/30m/ever)</Cell>
|
||||
{/if}
|
||||
<Cell head>Memory usage<br />(Windmill)</Cell>
|
||||
<Cell head>Limits</Cell>
|
||||
<Cell head>Version</Cell>
|
||||
<Cell head last>Liveness</Cell>
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#each worker_group[1] as [section, workers]}
|
||||
<tr class="border-t">
|
||||
<Cell
|
||||
first
|
||||
colspan={(!config || config?.dedicated_worker == undefined) && $superadmin
|
||||
? 11
|
||||
: 9}
|
||||
scope="colgroup"
|
||||
class="bg-surface-secondary/30 !py-1 border-b !text-xs"
|
||||
>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="min-w-64">
|
||||
Host:
|
||||
<span class="font-semibold">{section?.split(splitter)?.[0]}</span>
|
||||
</div>
|
||||
<span class="ml-4">IP: </span>
|
||||
<span class="font-semibold">{workers[0].ip}</span>
|
||||
|
||||
{#if workers?.length > 1}
|
||||
<span class="font-semibold ml-8">{workers?.length} Workers</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Cell>
|
||||
</tr>
|
||||
|
||||
{#if workers}
|
||||
{#each workers as { worker, custom_tags, last_ping, started_at, jobs_executed, last_job_id, last_job_workspace_id, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, occupancy_rate, wm_version, vcpus, memory, memory_usage, wm_memory_usage }}
|
||||
<tr>
|
||||
<Cell first>{worker}</Cell>
|
||||
<Cell>
|
||||
{#if custom_tags && custom_tags?.length > 2}
|
||||
{truncate(custom_tags?.join(', ') ?? '', 10)}
|
||||
<Tooltip>{custom_tags?.join(', ')}</Tooltip>
|
||||
{:else}
|
||||
{custom_tags?.join(', ') ?? ''}
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell>{last_ping != undefined ? last_ping + timeSinceLastPing : -1}s ago</Cell
|
||||
>
|
||||
<Cell>{displayDate(started_at)}</Cell>
|
||||
<Cell>{jobs_executed}</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
<Cell>
|
||||
{#if last_job_id}
|
||||
<a href={`/run/${last_job_id}?workspace=${last_job_workspace_id}`}>
|
||||
View last job
|
||||
</a>
|
||||
<br />
|
||||
(workspace {last_job_workspace_id})
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell>
|
||||
{displayOccupancyRate(occupancy_rate_15s)}/{displayOccupancyRate(
|
||||
occupancy_rate_5m
|
||||
)}/{displayOccupancyRate(occupancy_rate_30m)}/{displayOccupancyRate(
|
||||
occupancy_rate
|
||||
)}
|
||||
</Cell>
|
||||
{/if}
|
||||
<Cell>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>
|
||||
{memory_usage ? Math.round(memory_usage / 1024 / 1024) + 'MB' : '--'}
|
||||
</div>
|
||||
<div>
|
||||
({wm_memory_usage
|
||||
? Math.round(wm_memory_usage / 1024 / 1024) + 'MB'
|
||||
: '--'})
|
||||
</div>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>
|
||||
{vcpus ? (vcpus / 100000).toFixed(2) + ' vCPUs' : '--'}
|
||||
</div>
|
||||
<div>
|
||||
{memory ? Math.round(memory / 1024 / 1024) + 'MB' : '--'}
|
||||
</div>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="!text-2xs">
|
||||
{wm_version.split('-')[0]}<Tooltip>{wm_version}</Tooltip>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
<Badge
|
||||
color={last_ping != undefined
|
||||
? last_ping < 60
|
||||
? 'green'
|
||||
: 'red'
|
||||
: 'gray'}
|
||||
>
|
||||
{last_ping != undefined ? (last_ping < 60 ? 'Alive' : 'Dead') : 'Unknown'}
|
||||
</Badge>
|
||||
</Cell>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const worker_group = Object.entries(workerGroups ?? {})
|
||||
.filter((x) => !groupedWorkers.some((y) => y[0] == x[0]))
|
||||
.find((x) => x[0] == selectedTab)}
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
{#each groupedWorkers.map((x) => x[0]) as name (name)}
|
||||
{@const worker_group = groupedWorkers.find((x) => x[0] == name)}
|
||||
|
||||
{#if worker_group}
|
||||
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
|
||||
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
|
||||
)}
|
||||
<Tab value={worker_group[0]}>
|
||||
{`${worker_group[0]} - ${pluralize(activeWorkers?.length, 'worker')}`}
|
||||
<Tooltip>Number of workers active in the last 15s</Tooltip>
|
||||
</Tab>
|
||||
{:else}
|
||||
<Tab value={name}>
|
||||
{name} (0 worker)
|
||||
</Tab>
|
||||
{/if}
|
||||
{/each}
|
||||
</Tabs>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
{#if worker_group}
|
||||
{@const config = (workerGroups ?? {})[worker_group[0]]}
|
||||
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
|
||||
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
|
||||
)}
|
||||
<WorkspaceGroup
|
||||
{customTags}
|
||||
name={worker_group[0]}
|
||||
workers={worker_group[1]}
|
||||
{config}
|
||||
on:reload={() => {
|
||||
loadWorkerGroups()
|
||||
}}
|
||||
name={worker_group[0]}
|
||||
config={worker_group[1]}
|
||||
activeWorkers={0}
|
||||
activeWorkers={activeWorkers?.length ?? 0}
|
||||
{defaultTagPerWorkspace}
|
||||
/>
|
||||
<div class="text-xs text-tertiary"> No workers currently in this worker group </div>
|
||||
|
||||
<div class="flex flex-row items-center gap-2 relative my-2">
|
||||
<input
|
||||
class="max-w-80 border rounded-md !pl-8"
|
||||
placeholder="Search workers by name..."
|
||||
autocomplete="off"
|
||||
bind:value={search}
|
||||
/>
|
||||
<Search class="absolute left-2 " size={14} />
|
||||
</div>
|
||||
{#if worker_group?.[1].length == 0 && search}
|
||||
<div class="text-xs text-tertiary">
|
||||
No workers found. Reset the search to see all workers.
|
||||
</div>
|
||||
{:else}
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Worker</Cell>
|
||||
<Cell head>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
Worker Tags
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups#assign-custom-worker-groups"
|
||||
>
|
||||
If defined, the workers only pull jobs with the same corresponding tag
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell head>Last ping</Cell>
|
||||
<Cell head>Worker start</Cell>
|
||||
<Cell head>Jobs ran</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
<Cell head>Last job</Cell>
|
||||
<Cell head>Occupancy rate<br />(15s/5m/30m/ever)</Cell>
|
||||
{/if}
|
||||
<Cell head>Memory usage<br />(Windmill)</Cell>
|
||||
<Cell head>Limits</Cell>
|
||||
<Cell head>Version</Cell>
|
||||
<Cell head last>Liveness</Cell>
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#each worker_group[1] as [section, workers]}
|
||||
<tr class="border-t">
|
||||
<Cell
|
||||
first
|
||||
colspan={(!config || config?.dedicated_worker == undefined) && $superadmin
|
||||
? 11
|
||||
: 9}
|
||||
scope="colgroup"
|
||||
class="bg-surface-secondary/30 !py-1 border-b !text-xs"
|
||||
>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="min-w-64">
|
||||
Host:
|
||||
<span class="font-semibold">{section?.split(splitter)?.[0]}</span>
|
||||
</div>
|
||||
<span class="ml-4">IP: </span>
|
||||
<span class="font-semibold">{workers[0].ip}</span>
|
||||
|
||||
{#if workers?.length > 1}
|
||||
<span class="font-semibold ml-8">{workers?.length} Workers</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Cell>
|
||||
</tr>
|
||||
|
||||
{#if workers}
|
||||
{#each workers as { worker, custom_tags, last_ping, started_at, jobs_executed, last_job_id, last_job_workspace_id, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, occupancy_rate, wm_version, vcpus, memory, memory_usage, wm_memory_usage }}
|
||||
<tr>
|
||||
<Cell first>{worker}</Cell>
|
||||
<Cell>
|
||||
{#if custom_tags && custom_tags?.length > 2}
|
||||
{truncate(custom_tags?.join(', ') ?? '', 10)}
|
||||
<Tooltip>{custom_tags?.join(', ')}</Tooltip>
|
||||
{:else}
|
||||
{custom_tags?.join(', ') ?? ''}
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell
|
||||
>{last_ping != undefined ? last_ping + timeSinceLastPing : -1}s ago</Cell
|
||||
>
|
||||
<Cell>{displayDate(started_at)}</Cell>
|
||||
<Cell>{jobs_executed}</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
<Cell>
|
||||
{#if last_job_id}
|
||||
<a href={`/run/${last_job_id}?workspace=${last_job_workspace_id}`}>
|
||||
View last job
|
||||
</a>
|
||||
<br />
|
||||
(workspace {last_job_workspace_id})
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell>
|
||||
{displayOccupancyRate(occupancy_rate_15s)}/{displayOccupancyRate(
|
||||
occupancy_rate_5m
|
||||
)}/{displayOccupancyRate(occupancy_rate_30m)}/{displayOccupancyRate(
|
||||
occupancy_rate
|
||||
)}
|
||||
</Cell>
|
||||
{/if}
|
||||
<Cell>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>
|
||||
{memory_usage ? Math.round(memory_usage / 1024 / 1024) + 'MB' : '--'}
|
||||
</div>
|
||||
<div>
|
||||
({wm_memory_usage
|
||||
? Math.round(wm_memory_usage / 1024 / 1024) + 'MB'
|
||||
: '--'})
|
||||
</div>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>
|
||||
{vcpus ? (vcpus / 100000).toFixed(2) + ' vCPUs' : '--'}
|
||||
</div>
|
||||
<div>
|
||||
{memory ? Math.round(memory / 1024 / 1024) + 'MB' : '--'}
|
||||
</div>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="!text-2xs">
|
||||
{wm_version.split('-')[0]}<Tooltip>{wm_version}</Tooltip>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
<Badge
|
||||
color={last_ping != undefined
|
||||
? last_ping < 60
|
||||
? 'green'
|
||||
: 'red'
|
||||
: 'gray'}
|
||||
>
|
||||
{last_ping != undefined
|
||||
? last_ping < 60
|
||||
? 'Alive'
|
||||
: 'Dead'
|
||||
: 'Unknown'}
|
||||
</Badge>
|
||||
</Cell>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const worker_group = Object.entries(workerGroups ?? {})
|
||||
.filter((x) => !groupedWorkers.some((y) => y[0] == x[0]))
|
||||
.find((x) => x[0] == selectedTab)}
|
||||
|
||||
{#if worker_group}
|
||||
<WorkspaceGroup
|
||||
{customTags}
|
||||
workers={worker_group[1]}
|
||||
on:reload={() => {
|
||||
loadWorkerGroups()
|
||||
}}
|
||||
name={worker_group[0]}
|
||||
config={worker_group[1]}
|
||||
activeWorkers={0}
|
||||
/>
|
||||
<div class="text-xs text-tertiary"> No workers currently in this worker group </div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="pb-20" />
|
||||
<AutoscalingEvents worker_group={selectedTab} />
|
||||
{:else}
|
||||
<div class="flex flex-col">
|
||||
{#each new Array(4) as _}
|
||||
<Skeleton layout={[[8], 1]} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
</div>
|
||||
<div class="pb-20" />
|
||||
<AutoscalingEvents worker_group={selectedTab} />
|
||||
{:else}
|
||||
<div class="flex flex-col">
|
||||
{#each new Array(4) as _}
|
||||
<Skeleton layout={[[8], 1]} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user