feat: add run deletion, session reopen, and feed merging UI
This commit is contained in:
parent
3fa5a1441a
commit
a6af6e4b98
4 changed files with 107 additions and 0 deletions
|
|
@ -88,6 +88,15 @@ export async function stopRun(sessionId: string, runId: string): Promise<Session
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteRun(sessionId: string, runId: string): Promise<void> {
|
||||||
|
await api.delete(`/sessions/${sessionId}/runs/${runId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reopenSession(id: string): Promise<UseCaseSession> {
|
||||||
|
const res = await api.post<UseCaseSession>(`/sessions/${id}/reopen`)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
export async function completeSession(id: string): Promise<UseCaseSession> {
|
export async function completeSession(id: string): Promise<UseCaseSession> {
|
||||||
const res = await api.post<UseCaseSession>(`/sessions/${id}/complete`)
|
const res = await api.post<UseCaseSession>(`/sessions/${id}/complete`)
|
||||||
return res.data
|
return res.data
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,40 @@ export const useSessionsStore = defineStore('sessions', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteRun(sessionId: string, runId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await api.deleteRun(sessionId, runId)
|
||||||
|
if (currentSession.value?.id === sessionId) {
|
||||||
|
currentSession.value.runs = currentSession.value.runs.filter((r) => r.id !== runId)
|
||||||
|
const sIdx = sessions.value.findIndex((s) => s.id === sessionId)
|
||||||
|
if (sIdx !== -1) {
|
||||||
|
sessions.value[sIdx] = { ...sessions.value[sIdx], runs: [...currentSession.value.runs] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to delete run'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reopenSession(id: string): Promise<UseCaseSession | null> {
|
||||||
|
try {
|
||||||
|
const session = await api.reopenSession(id)
|
||||||
|
const idx = sessions.value.findIndex((s) => s.id === id)
|
||||||
|
if (idx !== -1) {
|
||||||
|
sessions.value[idx] = session
|
||||||
|
}
|
||||||
|
if (currentSession.value?.id === id) {
|
||||||
|
currentSession.value = session
|
||||||
|
}
|
||||||
|
return session
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to reopen session'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessions,
|
sessions,
|
||||||
currentSession,
|
currentSession,
|
||||||
|
|
@ -130,7 +164,9 @@ export const useSessionsStore = defineStore('sessions', () => {
|
||||||
deleteSession,
|
deleteSession,
|
||||||
startRun,
|
startRun,
|
||||||
stopRun,
|
stopRun,
|
||||||
|
deleteRun,
|
||||||
completeSession,
|
completeSession,
|
||||||
|
reopenSession,
|
||||||
reAnalyze,
|
reAnalyze,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ export interface CreateSessionRequest {
|
||||||
export interface StartRunRequest {
|
export interface StartRunRequest {
|
||||||
feed_name: string
|
feed_name: string
|
||||||
label?: string | null
|
label?: string | null
|
||||||
|
merge_feeds?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Use Cases ──
|
// ── Use Cases ──
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,18 @@ const store = useSessionsStore()
|
||||||
const showStartRun = ref(false)
|
const showStartRun = ref(false)
|
||||||
const runFeedName = ref('ksudp')
|
const runFeedName = ref('ksudp')
|
||||||
const runLabel = ref('')
|
const runLabel = ref('')
|
||||||
|
const runMergeEnabled = ref(false)
|
||||||
|
const runMergeFeeds = ref<string[]>([])
|
||||||
|
|
||||||
const sessionId = computed(() => props.id ?? (route.params.id as string))
|
const sessionId = computed(() => props.id ?? (route.params.id as string))
|
||||||
const session = computed(() => store.currentSession)
|
const session = computed(() => store.currentSession)
|
||||||
|
|
||||||
const feedOptions = ['ksudp', 'telemetry_tool', 'pcars']
|
const feedOptions = ['ksudp', 'telemetry_tool', 'pcars']
|
||||||
|
|
||||||
|
const mergeFeedOptions = computed(() => {
|
||||||
|
return feedOptions.filter((f) => f !== runFeedName.value)
|
||||||
|
})
|
||||||
|
|
||||||
const hasActiveRun = computed(() => {
|
const hasActiveRun = computed(() => {
|
||||||
return session.value?.runs.some((r) => r.status === 'recording') ?? false
|
return session.value?.runs.some((r) => r.status === 'recording') ?? false
|
||||||
})
|
})
|
||||||
|
|
@ -61,10 +67,13 @@ async function handleStartRun() {
|
||||||
const run = await store.startRun(sessionId.value, {
|
const run = await store.startRun(sessionId.value, {
|
||||||
feed_name: runFeedName.value,
|
feed_name: runFeedName.value,
|
||||||
label: runLabel.value || undefined,
|
label: runLabel.value || undefined,
|
||||||
|
merge_feeds: runMergeEnabled.value && runMergeFeeds.value.length > 0 ? runMergeFeeds.value : undefined,
|
||||||
})
|
})
|
||||||
if (run) {
|
if (run) {
|
||||||
showStartRun.value = false
|
showStartRun.value = false
|
||||||
runLabel.value = ''
|
runLabel.value = ''
|
||||||
|
runMergeEnabled.value = false
|
||||||
|
runMergeFeeds.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +111,16 @@ async function handleDelete() {
|
||||||
router.push('/sessions')
|
router.push('/sessions')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDeleteRun(runId: string, runLabel: string) {
|
||||||
|
if (confirm(`Delete run "${runLabel || 'Untitled Run'}"?`)) {
|
||||||
|
await store.deleteRun(sessionId.value, runId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleReopen() {
|
||||||
|
await store.reopenSession(sessionId.value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -181,6 +200,18 @@ async function handleDelete() {
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-primary btn-sm" @click="handleStartRun">Start</button>
|
<button class="btn btn-primary btn-sm" @click="handleStartRun">Start</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 mt-2">
|
||||||
|
<label class="merge-toggle">
|
||||||
|
<input type="checkbox" v-model="runMergeEnabled" />
|
||||||
|
<span>Merge with additional feeds</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div v-if="runMergeEnabled" class="flex gap-2 items-center mt-1">
|
||||||
|
<label v-for="f in mergeFeedOptions" :key="f" class="merge-feed-option">
|
||||||
|
<input type="checkbox" :value="f" v-model="runMergeFeeds" />
|
||||||
|
<span>{{ f }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="session.runs.length === 0" class="text-secondary text-muted">
|
<div v-if="session.runs.length === 0" class="text-secondary text-muted">
|
||||||
|
|
@ -208,6 +239,12 @@ async function handleDelete() {
|
||||||
>
|
>
|
||||||
■ Stop
|
■ Stop
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-danger btn-sm"
|
||||||
|
@click="handleDeleteRun(run.id, run.label || '')"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -279,6 +316,9 @@ async function handleDelete() {
|
||||||
<button class="btn btn-secondary" @click="handleReAnalyze">
|
<button class="btn btn-secondary" @click="handleReAnalyze">
|
||||||
🔄 Re-analyze
|
🔄 Re-analyze
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-secondary" @click="handleReopen">
|
||||||
|
↩ Reopen Session
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -385,4 +425,25 @@ async function handleDelete() {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.merge-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-feed-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue