Sidebar rework (#490)
* feat(frontend): Add runs to landing page + fix responsive issues * feat(frontend): Sidebar done * feat(frontend): Align all pages to the new layout * feat(frontend): Make scripts and flows box clickable * feat(frontend): Revert the sidebar color * feat(frontend): Restore missing workspace menu + fix minor UI issues
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<div class="max-w-screen-lg flex grow w-full h-full xl:-ml-20 mr-2 lg:mr-0">
|
||||
<div class="grow w-full h-full mt-4">
|
||||
<div class="py-6">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -154,8 +154,8 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full h-full justify-between">
|
||||
<div class={`flex flex-col mb-96 m-auto w-full sm:w-3/4 lg:w-2/3`}>
|
||||
<div class="flex flex-row w-full h-full justify-between ">
|
||||
<div class={`flex flex-col mb-96 m-auto w-full`}>
|
||||
<!-- Nav between steps-->
|
||||
<div class="justify-between flex flex-row w-full my-4">
|
||||
<Breadcrumb>
|
||||
|
||||
@@ -103,9 +103,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-col h-screen max-w-screen-lg xl:-ml-20 xl:pl-4 w-full -mt-4 pt-4 md:mx-10 pr-2"
|
||||
>
|
||||
<div class="flex flex-col h-screen ">
|
||||
<!-- Nav between steps-->
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="justify-between flex flex-row drop-shadow-sm w-full mt-4">
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between"
|
||||
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between cursor-pointer"
|
||||
on:click={() => goto(`/flows/get/${flow.path}`)}
|
||||
>
|
||||
<div class="font-bold">{flow.summary}</div>
|
||||
|
||||
@@ -18,7 +19,7 @@
|
||||
class="inline-flex space-x-2 space-x-reverse flex-row-reverse w-full bottom-4 right-4 flex-wrap"
|
||||
>
|
||||
<button
|
||||
on:click={() => goto(`/flows/edit/${flow.path}?step=2`)}
|
||||
on:click|stopPropagation={() => goto(`/flows/edit/${flow.path}?step=2`)}
|
||||
type="button"
|
||||
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
|
||||
data-dismiss-target="#alert-additional-content-5"
|
||||
@@ -41,7 +42,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => goto(`/flows/run/${flow.path}`)}
|
||||
on:click|stopPropagation={() => goto(`/flows/run/${flow.path}`)}
|
||||
type="button"
|
||||
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
|
||||
data-dismiss-target="#alert-additional-content-5"
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between"
|
||||
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between cursor-pointer"
|
||||
on:click={() => goto(`/scripts/get/${script.hash}`)}
|
||||
>
|
||||
<div class="font-bold">{script.summary}</div>
|
||||
<div class="font-bold">{script.summary || script.path}</div>
|
||||
|
||||
<div class="inline-flex justify-between w-full">
|
||||
<div class="text-xs">{script.path}</div>
|
||||
@@ -31,7 +32,7 @@
|
||||
</div>
|
||||
<div class="inline-flex space-x-2 space-x-reverse flex-row-reverse w-full">
|
||||
<button
|
||||
on:click={() => goto(`/scripts/edit/${script.hash}?step=2`)}
|
||||
on:click|stopPropagation={() => goto(`/scripts/edit/${script.hash}?step=2`)}
|
||||
type="button"
|
||||
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
|
||||
data-dismiss-target="#alert-additional-content-5"
|
||||
@@ -54,7 +55,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => goto(`/scripts/run/${script.hash}`)}
|
||||
on:click|stopPropagation={() => goto(`/scripts/run/${script.hash}`)}
|
||||
type="button"
|
||||
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
|
||||
data-dismiss-target="#alert-additional-content-5"
|
||||
|
||||
54
frontend/src/lib/components/sidebar/MenuLink.svelte
Normal file
54
frontend/src/lib/components/sidebar/MenuLink.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { navigating } from '$app/stores'
|
||||
|
||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let label: string
|
||||
export let href: string
|
||||
export let icon: IconDefinition
|
||||
export let isCollapsed: boolean
|
||||
export let disabled: boolean = false
|
||||
|
||||
let isSelected = false
|
||||
|
||||
navigating.subscribe(() => {
|
||||
if (href === '/') {
|
||||
isSelected = window.location.pathname === href
|
||||
} else {
|
||||
isSelected = window.location.pathname.includes(href)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if !disabled}
|
||||
<a
|
||||
{href}
|
||||
class={classNames(
|
||||
'group flex items-center px-2 py-2 text-sm font-medium rounded-md h-8',
|
||||
isSelected ? 'bg-gray-50 text-gray-900' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
||||
)}
|
||||
target={href.includes('http') ? '_blank' : null}
|
||||
>
|
||||
<Icon
|
||||
data={icon}
|
||||
class={classNames(
|
||||
' flex-shrink-0 h-4 w-4',
|
||||
isSelected ? ' text-gray-700' : 'text-white group-hover:text-gray-500',
|
||||
isCollapsed ? '-mr-1' : 'mr-3'
|
||||
)}
|
||||
/>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<span
|
||||
class={classNames(
|
||||
'whitespace-pre text-white',
|
||||
isSelected ? ' text-gray-700 font-bold' : 'text-white group-hover:text-gray-500'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
73
frontend/src/lib/components/sidebar/SidebarContent.svelte
Normal file
73
frontend/src/lib/components/sidebar/SidebarContent.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
faBookOpen,
|
||||
faCalendar,
|
||||
faCode,
|
||||
faCubes,
|
||||
faEye,
|
||||
faHomeAlt,
|
||||
faPlay,
|
||||
faRobot,
|
||||
faUsersCog,
|
||||
faWallet,
|
||||
faWind,
|
||||
faCog
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import MenuLink from './MenuLink.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
const mainMenuLinks = [
|
||||
{ label: 'Home', href: '/', icon: faHomeAlt },
|
||||
{ label: 'Scripts', href: '/scripts', icon: faCode },
|
||||
{ label: 'Flows', href: '/flows', icon: faWind },
|
||||
{ label: 'Runs', href: '/runs', icon: faPlay },
|
||||
{ label: 'Schedules', href: '/schedules', icon: faCalendar },
|
||||
{ label: 'Variables', href: '/variables', icon: faWallet },
|
||||
{ label: 'Resources', href: '/resources', icon: faCubes }
|
||||
]
|
||||
|
||||
$: secondaryMenuLinks = [
|
||||
{ label: 'Workers', href: '/workers', icon: faRobot },
|
||||
{ label: 'Groups', href: '/groups', icon: faUsersCog },
|
||||
{ label: 'Audit Logs', href: '/audit_logs', icon: faEye },
|
||||
{
|
||||
label: 'Workspace',
|
||||
href: '/workspace_settings',
|
||||
icon: faCog,
|
||||
disabled: !$userStore?.is_admin
|
||||
}
|
||||
]
|
||||
|
||||
const thirdMenuLinks = [
|
||||
{ label: 'Documentation', href: 'https://docs.windmill.dev/docs/intro/', icon: faBookOpen },
|
||||
{ label: 'Feedbacks?', href: 'https://discord.gg/V7PM2YHsPB', icon: faDiscord },
|
||||
{
|
||||
label: 'Issues?',
|
||||
href: 'https://github.com/windmill-labs/windmill/issues/new',
|
||||
icon: faGithub
|
||||
}
|
||||
]
|
||||
|
||||
export let isCollapsed: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="flex-1 flex flex-col py-4 overflow-y-auto overflow-x-hidden">
|
||||
<nav class="h-full flex justify-between flex-col px-2">
|
||||
<div class="space-y-2">
|
||||
{#each mainMenuLinks as menuLink}
|
||||
<MenuLink {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
<div class="h-8" />
|
||||
{#each secondaryMenuLinks as menuLink}
|
||||
<MenuLink {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each thirdMenuLinks as menuLink}
|
||||
<MenuLink {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
102
frontend/src/lib/components/sidebar/UserMenu.svelte
Normal file
102
frontend/src/lib/components/sidebar/UserMenu.svelte
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import { logout } from '$lib/logout'
|
||||
|
||||
import { userStore, usersWorkspaceStore, superadmin } from '$lib/stores'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faCog, faCrown, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { scale } from 'svelte/transition'
|
||||
|
||||
let show = false
|
||||
let menu: HTMLDivElement
|
||||
|
||||
export let isCollapsed: boolean = false
|
||||
|
||||
const handleOutsideClick = (event) => {
|
||||
if (show && !menu.contains(event.target)) {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleEscape = (event) => {
|
||||
if (show && event.key === 'Escape') {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('click', handleOutsideClick, false)
|
||||
document.addEventListener('keyup', handleEscape, false)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleOutsideClick, false)
|
||||
document.removeEventListener('keyup', handleEscape, false)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative" bind:this={menu}>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class={classNames(
|
||||
'group w-full flex items-center text-white hover:bg-gray-50 hover:text-gray-900 focus:outline-none px-2 py-2 text-sm font-medium rounded-md h-8 '
|
||||
)}
|
||||
on:click={() => (show = !show)}
|
||||
>
|
||||
<Icon
|
||||
data={faUser}
|
||||
class={classNames('flex-shrink-0 h-4 w-4', isCollapsed ? '-mr-1' : 'mr-2')}
|
||||
/>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<span class={classNames('whitespace-pre ')}>
|
||||
{$userStore?.username ?? ($superadmin ? $superadmin : '___')}
|
||||
{#if $userStore?.is_admin}
|
||||
<Icon data={faCrown} scale={0.6} />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if show}
|
||||
<div
|
||||
in:scale={{ duration: 100, start: 0.95 }}
|
||||
out:scale={{ duration: 75, start: 0.95 }}
|
||||
class="z-50 origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="px-4 py-3" role="none">
|
||||
<p class="text-sm" role="none">Signed in as</p>
|
||||
<p class="text-sm font-medium text-gray-900 truncate" role="none">
|
||||
{$usersWorkspaceStore?.email}
|
||||
</p>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<a
|
||||
href="/user/settings"
|
||||
class="text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
Account settings
|
||||
</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<button
|
||||
type="button"
|
||||
class="text-gray-700 block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
on:click={() => logout()}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
109
frontend/src/lib/components/sidebar/WorkspaceMenu.svelte
Normal file
109
frontend/src/lib/components/sidebar/WorkspaceMenu.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore, usersWorkspaceStore } from '$lib/stores'
|
||||
import { classNames } from '$lib/utils'
|
||||
import {
|
||||
faBuilding,
|
||||
faCog,
|
||||
faFolder,
|
||||
faFolderOpen,
|
||||
faFolderTree
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { scale } from 'svelte/transition'
|
||||
|
||||
let show = false
|
||||
let menu: HTMLDivElement
|
||||
|
||||
export let isCollapsed: boolean = false
|
||||
|
||||
const handleOutsideClick = (event) => {
|
||||
if (show && !menu.contains(event.target)) {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleEscape = (event) => {
|
||||
if (show && event.key === 'Escape') {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('click', handleOutsideClick, false)
|
||||
document.addEventListener('keyup', handleEscape, false)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleOutsideClick, false)
|
||||
document.removeEventListener('keyup', handleEscape, false)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative" bind:this={menu}>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class={classNames(
|
||||
'group w-full flex items-center text-white hover:bg-gray-50 hover:text-gray-900 focus:ring-4 focus:outline-none focus:ring-gray-300 px-2 py-2 text-sm font-medium rounded-md h-8 '
|
||||
)}
|
||||
on:click={() => (show = !show)}
|
||||
>
|
||||
<Icon
|
||||
data={faBuilding}
|
||||
class={classNames('flex-shrink-0 h-4 w-4', isCollapsed ? '-mr-1' : 'mr-2')}
|
||||
/>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<span class={classNames('whitespace-pre')}> {$workspaceStore} </span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if show}
|
||||
<div
|
||||
in:scale={{ duration: 100, start: 0.95 }}
|
||||
out:scale={{ duration: 75, start: 0.95 }}
|
||||
class="z-50 origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="py-1" role="none">
|
||||
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
|
||||
<button
|
||||
on:click={() => {
|
||||
workspaceStore.set(workspace.id)
|
||||
show = false
|
||||
}}
|
||||
class="block px-4 py-2 text-xs text-gray-500 "
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="text-gray-300 font-mono pr-1 text-xs">{workspace.id}</span>
|
||||
{workspace.name}
|
||||
</button>
|
||||
{/each}
|
||||
<a
|
||||
href="/user/create_workspace"
|
||||
class="text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
Create new workspace
|
||||
</a>
|
||||
<a
|
||||
href="/user/workspaces"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('workspace')
|
||||
}}
|
||||
class="text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
See all workspaces & invites
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,8 +54,9 @@ export function displayDate(dateString: string | undefined): string {
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString()}`
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString()}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,3 +557,7 @@ export function scriptToHubUrl(
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export function classNames(...classes: string[]): string {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
@@ -1,361 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { OpenAPI } from '$lib/gen'
|
||||
import { logout } from '$lib/logout'
|
||||
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
faBookOpen,
|
||||
faCalendar,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faCog,
|
||||
faCrown,
|
||||
faCubes,
|
||||
faEye,
|
||||
faHome,
|
||||
faPlay,
|
||||
faRobot,
|
||||
faScroll,
|
||||
faUser,
|
||||
faUsersCog,
|
||||
faWallet,
|
||||
faWind
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { onMount } from 'svelte'
|
||||
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import UserMenu from '$lib/components/sidebar/UserMenu.svelte'
|
||||
import { OpenAPI } from '$lib/gen'
|
||||
import { classNames } from '$lib/utils'
|
||||
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
import '../app.css'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
|
||||
let menuOpen = false
|
||||
let workspacePickerOpen = false
|
||||
let isMobile = false
|
||||
let viewportWidth = 3000
|
||||
let isCollapsed = false
|
||||
|
||||
function openMenu(): void {
|
||||
menuOpen = true
|
||||
let innerWidth = window.innerWidth
|
||||
|
||||
function disableCollapse() {
|
||||
isCollapsed = false
|
||||
}
|
||||
|
||||
function handleClickOutside(event: any): void {
|
||||
if (isMobile || viewportWidth < 640) {
|
||||
isCollapsed = true
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickOutsideMenu(event: any): void {
|
||||
menuOpen = false
|
||||
}
|
||||
function handleClickOutsideWorkspacePicker(event: any): void {
|
||||
workspacePickerOpen = false
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
|
||||
//Mobile
|
||||
isCollapsed = isMobile
|
||||
})
|
||||
$: innerWidth < 768 && disableCollapse()
|
||||
</script>
|
||||
|
||||
<div bind:clientWidth={viewportWidth} class="h-full max-w-screen">
|
||||
<nav
|
||||
use:clickOutside
|
||||
on:click_outside={handleClickOutside}
|
||||
class="flex flex-col fixed h-screen {isCollapsed
|
||||
? 'w-8'
|
||||
: 'w-36'} bg-blue-500 rounded-sm text-white z-10"
|
||||
>
|
||||
<div class="shrink">
|
||||
<button
|
||||
class="w-full flex flex-row-reverse transform hover:translate-x-1 transition-transform ease-in duration-200"
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
>
|
||||
<div class="pt-1 pr-3">
|
||||
<Icon data={isCollapsed ? faChevronRight : faChevronLeft} scale={0.9} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="flex flex-col {isCollapsed ? 'items-center' : 'px-6'} bg-transparent pb-1">
|
||||
<li class="z-20 font-medium items-center bg-transparent">
|
||||
<div
|
||||
class="flex justify-center"
|
||||
use:clickOutside
|
||||
on:click_outside={handleClickOutsideWorkspacePicker}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex text-sm focus:outline-none bg-transparent"
|
||||
id="user-menu-button"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<div
|
||||
class="flex flex-row items-center w-full justify-content"
|
||||
on:click={() => {
|
||||
workspacePickerOpen = true
|
||||
}}
|
||||
>
|
||||
<span class:hidden={isCollapsed} class="pr-2 font-mono text-xs flex"
|
||||
>{$workspaceStore ?? '_______'}
|
||||
<Icon class="text-white float-right mt-1 pl-1" data={faChevronDown} scale={0.6} />
|
||||
</span>
|
||||
<span class:hidden={!isCollapsed}>W</span>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="absolute {isCollapsed
|
||||
? 'left-4'
|
||||
: 'left-20'} -top-2 mt-2 w-52 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none {workspacePickerOpen
|
||||
? 'visible'
|
||||
: 'invisible'} z-40"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
|
||||
<button
|
||||
on:click={() => {
|
||||
workspaceStore.set(workspace.id)
|
||||
workspacePickerOpen = false
|
||||
}}
|
||||
class="block px-4 py-2 text-xs text-gray-500 "
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
>
|
||||
<span class="text-gray-300 font-mono pr-1 text-xs">{workspace.id}</span
|
||||
>{workspace.name}
|
||||
</button>
|
||||
{/each}
|
||||
<a
|
||||
href="/user/create_workspace"
|
||||
class="block px-4 py-2 text-blue-600 text-left text-xs "
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
>
|
||||
<span class="text-gray-300 font-mono pr-1">+</span>Create new workspace</a
|
||||
>
|
||||
<a
|
||||
href="/user/workspaces"
|
||||
class="block px-4 py-2 text-blue-600 text-left text-xs "
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('workspace')
|
||||
}}
|
||||
>
|
||||
See all workspaces & invites</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="flex flex-col max-h-12 mt-2 {isCollapsed ? 'items-center' : 'px-6 '} ">
|
||||
<li class="relative z-30 font-medium">
|
||||
<div
|
||||
class="flex justify-center"
|
||||
use:clickOutside
|
||||
on:click_outside={handleClickOutsideMenu}
|
||||
on:click={openMenu}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex text-sm rounded-full focus:outline-none"
|
||||
id="user-menu-button"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<div class="mx-auto">
|
||||
<span class:hidden={isCollapsed} class="px-2 font-mono text-xs whitespace-nowrap">
|
||||
<Icon class="text-white" data={faUser} scale={0.6} />
|
||||
{$userStore?.username ?? ($superadmin ? $superadmin : '___')}
|
||||
{#if $userStore?.is_admin}
|
||||
<Icon class="text-white" data={faCrown} scale={0.6} />
|
||||
{/if}
|
||||
<Icon class="inline text-white mt-1 ml-1" data={faChevronDown} scale={0.6} />
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="absolute {isCollapsed
|
||||
? 'left-4'
|
||||
: 'left-20'} -top-5 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none {menuOpen
|
||||
? 'visible'
|
||||
: 'invisible'}"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="block px-4 py-2 text-sm text-gray-500">{$usersWorkspaceStore?.email}</span>
|
||||
<a
|
||||
href="/user/settings"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-1">User settings</a
|
||||
>
|
||||
<button
|
||||
on:click={() => logout()}
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2">Sign out</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<div class="border-t border-gray-300 border-opacity-30 my-2" />
|
||||
</ul>
|
||||
<div class="grow h-full" />
|
||||
<ul class="flex flex-col {isCollapsed ? 'items-center' : 'px-6'} ">
|
||||
<li>
|
||||
<a href="/" class="menu-link text-sm font-medium items-center ">
|
||||
<Icon class="text-white" data={faHome} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Home</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/scripts" class="menu-link text-sm font-medium items-center ">
|
||||
<Icon class="text-white" data={faScroll} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Scripts</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/flows" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faWind} scale={0.9} />
|
||||
<span class="pl-2 {isCollapsed ? 'hidden' : ''}">Flows</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/runs" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faPlay} scale={0.8} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Runs</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/schedules" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faCalendar} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Schedules</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/variables" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faWallet} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Variables</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/resources" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faCubes} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Resources</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svelte:window bind:innerWidth />
|
||||
|
||||
<div class="grow h-full" />
|
||||
<ul class="flex flex-col bg-blue {isCollapsed ? 'items-center' : 'px-6'} ">
|
||||
<div class="border-t border-gray-300 border-opacity-30" />
|
||||
{#if $superadmin}
|
||||
<li>
|
||||
<a href="/workers" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faRobot} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Workers</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
<a href="/groups" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faUsersCog} scale={0.9} />
|
||||
<span class="pl-2 t {isCollapsed ? 'hidden' : ''}">Groups</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/audit_logs" class="menu-link text-sm font-medium items-center">
|
||||
<Icon class="text-white" data={faEye} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Audit logs</span>
|
||||
</a>
|
||||
</li>
|
||||
{#if $userStore?.is_admin}
|
||||
<li>
|
||||
<a href="/workspace_settings" class="menu-link text-sm font-medium items-center pb-2">
|
||||
<Icon class="text-white" data={faCog} scale={0.9} />
|
||||
<span class=" pl-2 {isCollapsed ? 'hidden' : ''}">Workspace</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
<div class="border-t border-gray-300 border-opacity-30" />
|
||||
</ul>
|
||||
<div class="grow h-full" />
|
||||
<ul class="flex flex-col {isCollapsed ? 'items-center' : 'px-6'} block">
|
||||
<li class="">
|
||||
<a
|
||||
href="https://github.com/windmill-labs/windmill/issues/new"
|
||||
class="menu-link text-sm font-medium items-center"
|
||||
target="_blank"
|
||||
<div>
|
||||
<div
|
||||
class={classNames('relative md:hidden ', menuOpen ? 'z-40' : 'pointer-events-none')}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'fixed inset-0 bg-gray-600 bg-opacity-75 transition-opacity ease-linear duration-300',
|
||||
menuOpen ? 'opacity-100' : 'opacity-0 '
|
||||
)}
|
||||
/>
|
||||
|
||||
<div class="fixed inset-0 flex">
|
||||
<div
|
||||
class={classNames(
|
||||
'relative flex-1 flex flex-col max-w-xs w-full bg-white transition ease-in-out duration-300 transform',
|
||||
menuOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'absolute top-0 right-0 -mr-12 pt-2 ease-in-out duration-300',
|
||||
menuOpen ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<Icon class="text-white" data={faGithub} scale={0.9} />
|
||||
<span class="pl-2 {isCollapsed ? 'hidden' : ''}">Issue?</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a
|
||||
href="https://discord.gg/V7PM2YHsPB"
|
||||
class="menu-link text-sm font-medium items-center"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon class="text-white" data={faDiscord} scale={0.9} />
|
||||
<span class="pl-2 {isCollapsed ? 'hidden' : ''}">Feedback</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a
|
||||
href="https://docs.windmill.dev/docs/intro"
|
||||
class="menu-link text-sm font-medium items-center"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon class="text-white" data={faBookOpen} scale={0.9} />
|
||||
<span class="pl-2 {isCollapsed ? 'hidden' : ''}">Docs</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
menuOpen = !menuOpen
|
||||
}}
|
||||
class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-blue-500 h-full">
|
||||
<div class="flex items-center flex-shrink-0 p-4 font-extrabold text-white">Windmill</div>
|
||||
|
||||
<div class="px-2 py-4 space-y-2 border-y border-blue-400 ">
|
||||
<WorkspaceMenu />
|
||||
<UserMenu />
|
||||
</div>
|
||||
|
||||
<SidebarContent {isCollapsed} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'hidden md:flex md:flex-col md:fixed md:inset-y-0 transition-all ease-in-out duration-200 ',
|
||||
isCollapsed ? 'md:w-12' : 'md:w-48'
|
||||
)}
|
||||
>
|
||||
<div class="flex-1 flex flex-col min-h-0 shadow-lg bg-blue-500">
|
||||
<div class="flex items-center flex-shrink-0 p-4 font-extrabold text-white">
|
||||
{#if isCollapsed}
|
||||
W
|
||||
{:else}
|
||||
Windmill
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="px-2 py-4 space-y-2 border-y border-blue-400 ">
|
||||
<WorkspaceMenu {isCollapsed} />
|
||||
<UserMenu {isCollapsed} />
|
||||
</div>
|
||||
<SidebarContent {isCollapsed} />
|
||||
|
||||
<div class="flex-shrink-0 flex p-4 border-t border-blue-400">
|
||||
<button
|
||||
class="h-12 flex flex-row text-sm font-medium min-w-full px-5 items-center transform hover:translate-x-1 transition-transform ease-in duration-200"
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
>
|
||||
<div class="w-full -ml-4">
|
||||
<div class="flex flex-row justify-between w-full">
|
||||
<div class="px-3 {isCollapsed ? 'hidden' : ''}">Windmill</div>
|
||||
<div class="ml-4">
|
||||
<Icon data={isCollapsed ? faChevronRight : faChevronLeft} scale={0.9} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Icon
|
||||
data={faArrowLeft}
|
||||
class={classNames(
|
||||
'flex-shrink-0 h-4 w-4 transition-all ease-in-out duration-200 text-white',
|
||||
isCollapsed ? 'rotate-180' : 'rotate-0'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white antialiased text-gray-900 {isCollapsed
|
||||
? 'pl-10'
|
||||
: 'pl-36 ml-2'} flex h-full max-w-screen flex-col items-center"
|
||||
class={classNames(
|
||||
'flex flex-col flex-1 transition-all ease-in-out duration-200',
|
||||
isCollapsed ? 'md:pl-12' : 'md:pl-48'
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
<main>
|
||||
<div class="border-b w-full">
|
||||
<div
|
||||
class="py-2 px-2 sm:px-4 md:px-8 flex justify-between items-center shadow-sm max-w-6xl mx-auto md:hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
menuOpen = true
|
||||
}}
|
||||
class="h-8 w-8 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global[menu] {
|
||||
@apply text-white;
|
||||
}
|
||||
.menu-link {
|
||||
@apply flex flex-row h-10 transform hover:translate-x-1 transition-transform ease-in duration-200 text-gray-200 hover:text-white;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
|
||||
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
|
||||
import { initFlow } from '$lib/components/flows/flowStore'
|
||||
@@ -57,4 +59,6 @@
|
||||
loadFlow()
|
||||
</script>
|
||||
|
||||
<FlowBuilder />
|
||||
<CenteredPage>
|
||||
<FlowBuilder />
|
||||
</CenteredPage>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { decodeState, emptySchema } from '$lib/utils'
|
||||
import { initFlow } from '$lib/components/flows/flowStore'
|
||||
import { initFlowState } from '$lib/components/flows/flowState'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
|
||||
const initialState = $page.url.searchParams.get('state')
|
||||
let flowLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined
|
||||
@@ -54,4 +54,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<FlowBuilder {initialPath} />
|
||||
<CenteredPage>
|
||||
<FlowBuilder {initialPath} />
|
||||
</CenteredPage>
|
||||
|
||||
@@ -42,14 +42,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
const ressources = []
|
||||
const resources = []
|
||||
</script>
|
||||
|
||||
<CenteredPage>
|
||||
<div class="space-y-12 mx-8 my-16">
|
||||
<h1 class="text-xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl">
|
||||
Welcome to Windmill!
|
||||
</h1>
|
||||
<h1 class="text-xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl">Home</h1>
|
||||
<div class="space-y-12">
|
||||
<div>
|
||||
<h2
|
||||
class="mb-4 text-lg font-extrabold tracking-tight leading-none text-gray-900 md:text-2xl border-b py-2"
|
||||
@@ -127,11 +125,11 @@
|
||||
class="mb-4 text-lg font-extrabold tracking-tight leading-none text-gray-900 md:text-2xl border-b py-2 "
|
||||
>
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r to-blue-500 from-blue-600">
|
||||
Ressources
|
||||
Resources
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{#if ressources.length === 0}
|
||||
{#if resources.length === 0}
|
||||
<RessourceGettingStarted />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { decodeState, emptySchema, getScriptByPath, sendUserToast } from '$lib/utils'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
|
||||
// Default
|
||||
let schema: Schema = emptySchema()
|
||||
@@ -76,4 +77,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScriptBuilder {script} />
|
||||
<CenteredPage>
|
||||
<ScriptBuilder {script} />
|
||||
</CenteredPage>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
|
||||
import { decodeState } from '$lib/utils'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
|
||||
const initialState = $page.url.searchParams.get('state')
|
||||
let scriptLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined
|
||||
@@ -40,5 +41,7 @@
|
||||
</script>
|
||||
|
||||
{#if script}
|
||||
<ScriptBuilder {initialPath} {script} />
|
||||
<CenteredPage>
|
||||
<ScriptBuilder {initialPath} {script} />
|
||||
</CenteredPage>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user