mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
feat: refactor favorite menu
This commit is contained in:
@@ -2337,6 +2337,13 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: starred_only
|
||||
description: |
|
||||
(default false)
|
||||
show only the starred items
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All available apps
|
||||
|
||||
@@ -25,6 +25,7 @@ use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{types::Uuid, FromRow};
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
apps::ListAppQuery,
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
users::owner_to_token_owner,
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath},
|
||||
@@ -120,12 +121,13 @@ pub struct EditApp {
|
||||
async fn list_apps(
|
||||
authed: Authed,
|
||||
Query(pagination): Query<Pagination>,
|
||||
Query(lq): Query<ListAppQuery>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
) -> JsonResult<Vec<ListableApp>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
|
||||
let sqlb = SqlBuilder::select_from("app")
|
||||
let mut sqlb = SqlBuilder::select_from("app")
|
||||
.fields(&[
|
||||
"app.id",
|
||||
"app.workspace_id",
|
||||
@@ -154,6 +156,11 @@ async fn list_apps(
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
|
||||
if lq.starred_only.unwrap_or(false) {
|
||||
sqlb.and_where_is_not_null("favorite.path");
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableApp>(&sql)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListAppQuery {
|
||||
pub starred_only: Option<bool>,
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use std::net::SocketAddr;
|
||||
|
||||
use error::Error;
|
||||
|
||||
pub mod apps;
|
||||
pub mod error;
|
||||
pub mod external_ip;
|
||||
pub mod flow_status;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export let pickCallback: (path: string, f: string) => void
|
||||
export let loadItems: () => Promise<Item[] | undefined>
|
||||
export let extraField: string = 'path'
|
||||
export let extraField2: string | undefined
|
||||
export let extraField2: string | undefined = undefined
|
||||
export let itemName: string
|
||||
export let closeOnClick = true
|
||||
/** Displayed if the load function returns no items. */
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
}
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
$: kind = script.kind as 'script' | 'trigger' | 'approval' | undefined
|
||||
</script>
|
||||
|
||||
<UnsavedConfirmationModal />
|
||||
@@ -290,7 +291,7 @@
|
||||
bind:code={script.content}
|
||||
lang={script.language}
|
||||
{initialArgs}
|
||||
kind={script.kind}
|
||||
{kind}
|
||||
/>
|
||||
{:else if step === 3}
|
||||
<CenteredPage>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faCode, faStar, faTelevision, faWind } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import Menu from '../common/menu/Menu.svelte'
|
||||
|
||||
export let isCollapsed: boolean = false
|
||||
export let favoriteLinks = [] as {
|
||||
label: string
|
||||
href: string
|
||||
kind: 'script' | 'flow' | 'app'
|
||||
}[]
|
||||
</script>
|
||||
|
||||
<Menu placement="bottom-start" let:close noMinW>
|
||||
<button
|
||||
slot="trigger"
|
||||
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 '
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
data={faStar}
|
||||
class={classNames('flex-shrink-0 h-4 w-4', isCollapsed ? '-mr-1' : 'mr-2')}
|
||||
/>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<span class={classNames('whitespace-pre truncate')}>Favorites</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<div class="divide-y divide-gray-100 overflow-hidden" role="none">
|
||||
<table class="w-full">
|
||||
{#each favoriteLinks ?? [] as favorite (favorite.href)}
|
||||
<tr>
|
||||
<a href={favorite.href} on:click={close} class="inline-flex flex-row px-4 py-2">
|
||||
<td class="center-center">
|
||||
{#if favorite.kind == 'script'}
|
||||
<Icon data={faCode} />
|
||||
{:else if favorite.kind == 'flow'}
|
||||
<Icon data={faWind} />
|
||||
{:else if favorite.kind == 'app'}
|
||||
<Icon data={faTelevision} />
|
||||
{/if}
|
||||
</td>
|
||||
<td class="text-gray-800 ml-2 grow text-xs center-center truncate">{favorite.label}</td>
|
||||
</a>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
<div class="py-1" role="none">
|
||||
<div class="text-gray-600 block px-4 py-2 text-xs" role="menuitem" tabindex="-1">
|
||||
Add Scripts/Flows/Apps here by starring them
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
@@ -9,7 +9,6 @@
|
||||
faRobot,
|
||||
faUsersCog,
|
||||
faCog,
|
||||
faStar,
|
||||
faDollarSign
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
@@ -23,8 +22,6 @@
|
||||
{ label: 'Resources', href: '/resources', icon: faCubes }
|
||||
]
|
||||
|
||||
export let favoriteLinks = [] as { label: string; href: string }[]
|
||||
|
||||
$: secondaryMenuLinks = [
|
||||
{ label: 'Schedules', href: '/schedules', icon: faCalendar },
|
||||
{ label: 'Groups', href: '/groups', icon: faUsersCog },
|
||||
@@ -51,21 +48,13 @@
|
||||
export let isCollapsed: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="flex-1 flex flex-col py-4 overflow-x-hidden scrollbar-hidden">
|
||||
<div class="flex-1 flex flex-col pb-4 overflow-x-hidden scrollbar-hidden">
|
||||
<nav class="h-full flex justify-between flex-col px-2">
|
||||
<div class="space-y-1 pt-4">
|
||||
{#each mainMenuLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-md" {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
<div class="h-2" />
|
||||
<div class="max-h-40 overflow-y-auto max-w-xs truncate">
|
||||
<div class="flex flex-col max-h-min">
|
||||
{#each favoriteLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-xs max-w-xs truncate" {...menuLink} {isCollapsed} icon={faStar} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-2" />
|
||||
</div>
|
||||
<div class="space-1-2">
|
||||
{#each secondaryMenuLinks as menuLink (menuLink.href)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import UserMenu from '$lib/components/sidebar/UserMenu.svelte'
|
||||
import { FlowService, OpenAPI, ScriptService } from '$lib/gen'
|
||||
import { AppService, FlowService, OpenAPI, ScriptService } from '$lib/gen'
|
||||
import { classNames } from '$lib/utils'
|
||||
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
@@ -16,6 +16,7 @@
|
||||
import SuperadminSettings from '$lib/components/SuperadminSettings.svelte'
|
||||
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import FavoriteMenu from '$lib/components/sidebar/FavoriteMenu.svelte'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
let innerWidth = window.innerWidth
|
||||
|
||||
let favoriteLinks = [] as { label: string; href: string }[]
|
||||
let favoriteLinks = [] as { label: string; href: string; kind: 'app' | 'script' | 'flow' }[]
|
||||
$: $workspaceStore && $starStore && loadFavorites()
|
||||
|
||||
async function loadFavorites() {
|
||||
@@ -46,14 +47,25 @@
|
||||
workspace: $workspaceStore ?? '',
|
||||
starredOnly: true
|
||||
})
|
||||
const apps = await AppService.listApps({
|
||||
workspace: $workspaceStore ?? '',
|
||||
starredOnly: true
|
||||
})
|
||||
favoriteLinks = [
|
||||
...scripts.map((s) => ({
|
||||
label: s.summary || s.path,
|
||||
href: `/scripts/run/${s.hash}`
|
||||
href: `/scripts/run/${s.hash}`,
|
||||
kind: 'script' as 'script'
|
||||
})),
|
||||
...flows.map((f) => ({
|
||||
label: f.summary || f.path,
|
||||
href: `/flows/run/${f.path}`
|
||||
href: `/flows/run/${f.path}`,
|
||||
kind: 'flow' as 'flow'
|
||||
})),
|
||||
...apps.map((f) => ({
|
||||
label: f.summary || f.path,
|
||||
href: `/apps/get/${f.path}`,
|
||||
kind: 'app' as 'app'
|
||||
}))
|
||||
]
|
||||
}
|
||||
@@ -138,9 +150,10 @@
|
||||
on:user-settings={() => userSettings.openDrawer()}
|
||||
on:superadmin-settings={() => superadminSettings.openDrawer()}
|
||||
/>
|
||||
<FavoriteMenu {favoriteLinks} />
|
||||
</div>
|
||||
|
||||
<SidebarContent {favoriteLinks} {isCollapsed} />
|
||||
<SidebarContent {isCollapsed} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,7 +168,7 @@
|
||||
<div class="flex-1 flex flex-col min-h-0 shadow-lg bg-[#2e3440]">
|
||||
<button
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed
|
||||
goto('/')
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -170,7 +183,6 @@
|
||||
>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="px-2 py-4 space-y-2 border-y border-gray-300">
|
||||
<WorkspaceMenu {isCollapsed} />
|
||||
<UserMenu
|
||||
@@ -178,8 +190,9 @@
|
||||
on:superadmin-settings={() => superadminSettings.openDrawer()}
|
||||
{isCollapsed}
|
||||
/>
|
||||
<FavoriteMenu {favoriteLinks} />
|
||||
</div>
|
||||
<SidebarContent {favoriteLinks} {isCollapsed} />
|
||||
<SidebarContent {isCollapsed} />
|
||||
|
||||
<div class="flex-shrink-0 flex px-4 pb-3.5 pt-3 border-t border-gray-300">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user