Skip to content

Commit 2f3c89e

Browse files
committed
navigation improvements
1 parent c17f18c commit 2f3c89e

9 files changed

Lines changed: 57 additions & 135 deletions

File tree

frontend/src/components/app-routes.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { createComponent, createNestedNavigate, createNestedRouteLink, type NestedRoute } from '@furystack/shades'
1+
import {
2+
createComponent,
3+
createNestedNavigate,
4+
createNestedReplace,
5+
createNestedRouteLink,
6+
type NestedRoute,
7+
} from '@furystack/shades'
28
import type { MatchResult } from 'path-to-regexp'
39
import { Dashboard } from '../pages/dashboard/index.js'
410
import { ExportStack } from '../pages/import-export/export-stack.js'
@@ -8,7 +14,6 @@ import { CreateRepository } from '../pages/repositories/create-repository.js'
814
import { EditRepository } from '../pages/repositories/edit-repository.js'
915
import { RepositoriesList } from '../pages/repositories/repositories-list.js'
1016
import { ServiceDetail } from '../pages/services/service-detail/index.js'
11-
import { ServiceLogs } from '../pages/services/service-logs.js'
1217
import { ServicesList } from '../pages/services/services-list.js'
1318
import { UserSettings } from '../pages/settings/user-settings.js'
1419
import { CreateStack } from '../pages/stacks/create-stack.js'
@@ -59,24 +64,13 @@ export const appRoutes = {
5964
<CreateServiceWizard stackName={match.params.stackName} />
6065
),
6166
},
62-
'/stacks/:stackName/services/:serviceId/logs/:processUid': {
63-
component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string; processUid: string }> }) => (
64-
<ServiceLogs
65-
stackName={match.params.stackName}
66-
serviceId={match.params.serviceId}
67-
processUid={match.params.processUid}
68-
/>
69-
),
70-
},
71-
'/stacks/:stackName/services/:serviceId/logs': {
72-
component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string }> }) => (
73-
<ServiceLogs stackName={match.params.stackName} serviceId={match.params.serviceId} />
74-
),
75-
},
7667
'/stacks/:stackName/services/:serviceId': {
7768
component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string }> }) => (
7869
<ServiceDetail stackName={match.params.stackName} serviceId={match.params.serviceId} />
7970
),
71+
hash: ['overview', 'logs', 'history', 'files', 'configuration'] as const,
72+
query: ({ processUid }): { processUid?: string } | null =>
73+
typeof processUid === 'string' || processUid === undefined ? { processUid } : null,
8074
},
8175
'/stacks/:stackName/repositories': {
8276
component: ({ match }: { match: MatchResult<{ stackName: string }> }) => (
@@ -104,6 +98,8 @@ export const StackCraftNestedRouteLink = createNestedRouteLink<typeof appRoutes>
10498

10599
export const stackCraftNavigate = createNestedNavigate<typeof appRoutes>()
106100

101+
export const stackCraftReplace = createNestedReplace<typeof appRoutes>()
102+
107103
export type AppRoutePath = keyof typeof appRoutes
108104

109105
export type StaticAppRoutePath = {

frontend/src/components/layout/breadcrumbs.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,6 @@ const parseBreadcrumbs = (
4747
href: '/stacks/:stackName/services/:serviceId',
4848
params: { stackName, serviceId },
4949
})
50-
51-
const logSection = parts[4]
52-
if (logSection === 'logs') {
53-
const processUid = parts[5]
54-
if (processUid) {
55-
segments.push({
56-
label: 'Logs',
57-
href: '/stacks/:stackName/services/:serviceId/logs',
58-
params: { stackName, serviceId },
59-
})
60-
segments.push({ label: `${processUid.slice(0, 8)}…` })
61-
} else {
62-
segments.push({ label: 'Logs' })
63-
}
64-
}
6550
} else if (serviceId === 'create') {
6651
segments.push({ label: 'Create' })
6752
} else if (serviceId === 'wizard') {

frontend/src/components/service-table.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ export const ServiceTable = Shade<ServiceTableProps>({
210210
) : null}
211211
{/* Logs */}
212212
<StackCraftNestedRouteLink
213-
path="/stacks/:stackName/services/:serviceId/logs"
213+
path="/stacks/:stackName/services/:serviceId"
214214
params={{ stackName: entry.stackName, serviceId: entry.id }}
215+
hash="logs"
215216
>
216217
<Button
217218
variant="text"

frontend/src/pages/services/service-detail/history-tab.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,13 @@ export const ServiceHistory = Shade<ServiceHistoryProps>({
144144
size="small"
145145
onclick={() =>
146146
stackCraftNavigate(injector, {
147-
path: '/stacks/:stackName/services/:serviceId/logs/:processUid',
147+
path: '/stacks/:stackName/services/:serviceId',
148148
params: {
149149
stackName: props.stackName,
150150
serviceId: props.serviceId,
151-
processUid,
152151
},
152+
query: { processUid },
153+
hash: 'logs',
153154
})
154155
}
155156
>

frontend/src/pages/services/service-detail/index.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
StackDefinition,
2828
} from 'common'
2929

30+
import { stackCraftReplace } from '../../../components/app-routes.js'
3031
import { PrerequisiteSummaryChip } from '../../../components/prerequisite-summary-chip.js'
3132
import { SecretWarningsCard } from '../../../components/secret-warnings-card.js'
3233
import { ServiceStatusIndicator } from '../../../components/service-status-indicator.js'
@@ -55,22 +56,28 @@ type ServiceDetailProps = {
5556
export const ServiceDetail = Shade<ServiceDetailProps>({
5657
customElementName: 'shade-service-detail',
5758
render: (options) => {
58-
const { props, injector, useState } = options
59+
const { props, injector, useState, useObservable } = options
5960
const locationService = injector.getInstance(LocationService)
6061
const validTabs: TabId[] = ['overview', 'logs', 'history', 'files', 'configuration']
61-
const hashValue = locationService.onLocationHashChanged.getValue().replace('#', '')
62-
const searchState = locationService.onDeserializedLocationSearchChanged.getValue()
63-
const initialTab: TabId = validTabs.includes(hashValue as TabId)
62+
const [hashRaw] = useObservable('locationHash', locationService.onLocationHashChanged)
63+
const [searchState] = useObservable('locationSearch', locationService.onDeserializedLocationSearchChanged)
64+
const hashValue = hashRaw.replace('#', '')
65+
const processUid = typeof searchState.processUid === 'string' ? searchState.processUid : undefined
66+
const activeTab: TabId = validTabs.includes(hashValue as TabId)
6467
? (hashValue as TabId)
65-
: searchState.edit === true
66-
? 'configuration'
67-
: 'overview'
68-
const [activeTab, setActiveTabState] = useState<TabId>('activeTab', initialTab)
68+
: processUid
69+
? 'logs'
70+
: searchState.edit === true
71+
? 'configuration'
72+
: 'overview'
6973
const [isConfirmingDelete, setIsConfirmingDelete] = useState('isConfirmingDelete', false)
7074

7175
const setActiveTab = (tab: TabId) => {
72-
locationService.replace(`${window.location.pathname}#${tab}`)
73-
setActiveTabState(tab)
76+
stackCraftReplace(injector, {
77+
path: '/stacks/:stackName/services/:serviceId',
78+
params: { stackName: props.stackName, serviceId: props.serviceId },
79+
hash: tab,
80+
})
7481
}
7582

7683
const serviceState = useEntitySync(options, ServiceDefinition, props.serviceId)
@@ -237,6 +244,7 @@ export const ServiceDetail = Shade<ServiceDetailProps>({
237244
<Tabs
238245
activeKey={activeTab}
239246
onTabChange={(key) => setActiveTab(key as TabId)}
247+
containerStyle={{ flex: '1', minHeight: '0' }}
240248
tabs={[
241249
{
242250
header: <span>Overview</span>,
@@ -258,7 +266,7 @@ export const ServiceDetail = Shade<ServiceDetailProps>({
258266
{
259267
header: <span>Logs</span>,
260268
hash: 'logs',
261-
component: <LogsTab serviceId={service.id} stackName={service.stackName} />,
269+
component: <LogsTab serviceId={service.id} stackName={service.stackName} processUid={processUid} />,
262270
},
263271
{
264272
header: <span>History</span>,

frontend/src/pages/services/service-detail/logs-tab.tsx

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { createComponent, Shade } from '@furystack/shades'
22
import { Button, Icon, icons, NotyService, Paper } from '@furystack/shades-common-components'
33

4-
import { StackCraftNestedRouteLink } from '../../../components/app-routes.js'
54
import { LogViewer } from '../../../components/shared/log-viewer.js'
65
import { ServicesApiClient } from '../../../services/api-clients/services-api-client.js'
76

@@ -12,6 +11,7 @@ import { ServicesApiClient } from '../../../services/api-clients/services-api-cl
1211
type LogsTabProps = {
1312
serviceId: string
1413
stackName: string
14+
processUid?: string
1515
}
1616

1717
export const LogsTab = Shade<LogsTabProps>({
@@ -42,31 +42,25 @@ export const LogsTab = Shade<LogsTabProps>({
4242
}
4343

4444
return (
45-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
45+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', flex: '1', minHeight: '0' }}>
4646
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
47-
<h3 style={{ margin: '0' }}>Service Logs</h3>
48-
<div style={{ display: 'flex', gap: '8px' }}>
49-
<Button
50-
variant="outlined"
51-
size="small"
52-
color="error"
53-
onclick={() => void handleClearLogs()}
54-
startIcon={<Icon icon={icons.trash} size="small" />}
55-
>
56-
Clear Logs
57-
</Button>
58-
<StackCraftNestedRouteLink
59-
path="/stacks/:stackName/services/:serviceId/logs"
60-
params={{ stackName: props.stackName, serviceId: props.serviceId }}
61-
>
62-
<Button variant="outlined" size="small" startIcon={<Icon icon={icons.externalLink} size="small" />}>
63-
Full View
47+
<h3 style={{ margin: '0' }}>{props.processUid ? 'Process Logs' : 'Service Logs'}</h3>
48+
{!props.processUid ? (
49+
<div style={{ display: 'flex', gap: '8px' }}>
50+
<Button
51+
variant="outlined"
52+
size="small"
53+
color="error"
54+
onclick={() => void handleClearLogs()}
55+
startIcon={<Icon icon={icons.trash} size="small" />}
56+
>
57+
Clear Logs
6458
</Button>
65-
</StackCraftNestedRouteLink>
66-
</div>
59+
</div>
60+
) : null}
6761
</div>
68-
<Paper style={{ height: 'clamp(300px, 50vh, 600px)', overflow: 'hidden' }}>
69-
<LogViewer serviceId={props.serviceId} />
62+
<Paper style={{ flex: '1', minHeight: '0', overflow: 'hidden' }}>
63+
<LogViewer serviceId={props.serviceId} processUid={props.processUid} />
7064
</Paper>
7165
</div>
7266
)

frontend/src/pages/services/service-logs.tsx

Lines changed: 0 additions & 65 deletions
This file was deleted.

frontend/src/pages/stacks/stack-setup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,9 @@ export const StackSetup = Shade<StackSetupProps>({
273273
</Button>
274274
)}
275275
<StackCraftNestedRouteLink
276-
path="/stacks/:stackName/services/:serviceId/logs"
276+
path="/stacks/:stackName/services/:serviceId"
277277
params={{ stackName: props.stackName, serviceId: svc.id }}
278+
hash="logs"
278279
>
279280
<Button variant="outlined" size="small" startIcon={<Icon icon={icons.fileText} size="small" />}>
280281
Logs

frontend/src/pages/wizards/create-service-wizard/setup-step.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ export const SetupStep = Shade<SetupStepProps>({
124124
Retry
125125
</Button>
126126
<StackCraftNestedRouteLink
127-
path="/stacks/:stackName/services/:serviceId/logs"
127+
path="/stacks/:stackName/services/:serviceId"
128128
params={{ stackName: props.stackName, serviceId: props.serviceId }}
129+
hash="logs"
129130
>
130131
<Button variant="outlined" startIcon={<Icon icon={icons.fileText} size="small" />}>
131132
View Logs

0 commit comments

Comments
 (0)