feat: add "+ App" shortcut button to sidebar Applications header
Adds a subtle "+ App" button in the sidebar section header for quick app creation without navigating to the Deployments tab first. Only visible to OPERATOR and ADMIN roles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,34 @@
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appSectionWrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addAppBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sidebar-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.12s, background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addAppBtn:hover {
|
||||||
|
color: var(--amber);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.mainContent {
|
.mainContent {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '@cameleer/design-system';
|
} from '@cameleer/design-system';
|
||||||
import type { SearchResult, SidebarTreeNode, DropdownItem, ButtonGroupItem, ExchangeStatus } from '@cameleer/design-system';
|
import type { SearchResult, SidebarTreeNode, DropdownItem, ButtonGroupItem, ExchangeStatus } from '@cameleer/design-system';
|
||||||
import sidebarLogo from '@cameleer/design-system/assets/cameleer3-logo.svg';
|
import sidebarLogo from '@cameleer/design-system/assets/cameleer3-logo.svg';
|
||||||
import { Box, Settings, FileText, ChevronRight, Square, Pause, Star, X, User } from 'lucide-react';
|
import { Box, Settings, FileText, ChevronRight, Square, Pause, Star, X, User, Plus } from 'lucide-react';
|
||||||
import { AboutMeDialog } from './AboutMeDialog';
|
import { AboutMeDialog } from './AboutMeDialog';
|
||||||
import css from './LayoutShell.module.css';
|
import css from './LayoutShell.module.css';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
@@ -31,7 +31,7 @@ import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions
|
|||||||
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
|
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
|
||||||
import { useEnvironments } from '../api/queries/admin/environments';
|
import { useEnvironments } from '../api/queries/admin/environments';
|
||||||
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
|
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
|
||||||
import { useAuthStore, useIsAdmin } from '../auth/auth-store';
|
import { useAuthStore, useIsAdmin, useCanControl } from '../auth/auth-store';
|
||||||
import { useEnvironmentStore } from '../api/environment-store';
|
import { useEnvironmentStore } from '../api/environment-store';
|
||||||
import { useState, useMemo, useCallback, useEffect, useRef, createElement } from 'react';
|
import { useState, useMemo, useCallback, useEffect, useRef, createElement } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@@ -296,6 +296,7 @@ function LayoutContent() {
|
|||||||
|
|
||||||
// --- Role checks ----------------------------------------------------
|
// --- Role checks ----------------------------------------------------
|
||||||
const isAdmin = useIsAdmin();
|
const isAdmin = useIsAdmin();
|
||||||
|
const canControl = useCanControl();
|
||||||
|
|
||||||
// --- Environment filtering -----------------------------------------
|
// --- Environment filtering -----------------------------------------
|
||||||
const selectedEnv = useEnvironmentStore((s) => s.environment);
|
const selectedEnv = useEnvironmentStore((s) => s.environment);
|
||||||
@@ -692,23 +693,34 @@ function LayoutContent() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Applications section */}
|
{/* Applications section */}
|
||||||
<Sidebar.Section
|
<div className={css.appSectionWrap}>
|
||||||
icon={createElement(Box, { size: 16 })}
|
{canControl && (
|
||||||
label="Applications"
|
<button
|
||||||
open={appsOpen}
|
className={css.addAppBtn}
|
||||||
onToggle={toggleApps}
|
onClick={(e) => { e.stopPropagation(); navigate('/apps/new'); }}
|
||||||
>
|
title="Create App"
|
||||||
<SidebarTree
|
>
|
||||||
nodes={appTreeNodes}
|
<Plus size={12} /> App
|
||||||
selectedPath={effectiveSelectedPath}
|
</button>
|
||||||
isStarred={isStarred}
|
)}
|
||||||
onToggleStar={toggleStar}
|
<Sidebar.Section
|
||||||
filterQuery={filterQuery}
|
icon={createElement(Box, { size: 16 })}
|
||||||
persistKey="apps"
|
label="Applications"
|
||||||
autoRevealPath={sidebarRevealPath}
|
open={appsOpen}
|
||||||
onNavigate={handleSidebarNavigate}
|
onToggle={toggleApps}
|
||||||
/>
|
>
|
||||||
</Sidebar.Section>
|
<SidebarTree
|
||||||
|
nodes={appTreeNodes}
|
||||||
|
selectedPath={effectiveSelectedPath}
|
||||||
|
isStarred={isStarred}
|
||||||
|
onToggleStar={toggleStar}
|
||||||
|
filterQuery={filterQuery}
|
||||||
|
persistKey="apps"
|
||||||
|
autoRevealPath={sidebarRevealPath}
|
||||||
|
onNavigate={handleSidebarNavigate}
|
||||||
|
/>
|
||||||
|
</Sidebar.Section>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Starred section — only when there are starred items */}
|
{/* Starred section — only when there are starred items */}
|
||||||
{starredItems.length > 0 && (
|
{starredItems.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user