feat: download logs from backend

This commit is contained in:
Ruben Fiszel
2023-06-22 22:38:41 +02:00
parent 3a022b7863
commit 28094b29d6
14 changed files with 101 additions and 8 deletions
+21
View File
@@ -2901,6 +2901,27 @@
},
"query": "SELECT policy from app WHERE path = $1 AND workspace_id = $2"
},
"6f246196df45be4f1860f04c0fb25b1076c9c3fa30d632f4a90cb37a8f4d419b": {
"describe": {
"columns": [
{
"name": "logs",
"ordinal": 0,
"type_info": "Text"
}
],
"nullable": [
true
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT logs FROM completed_job WHERE id = $1 AND workspace_id = $2"
},
"701f215eb14ba67a79afea15d7effc0dd394ba6c4a72c95d4560c5a377015d4e": {
"describe": {
"columns": [],
+17
View File
@@ -3768,6 +3768,23 @@ paths:
schema:
$ref: "#/components/schemas/Job"
/w/{workspace}/jobs_u/get_logs/{id}:
get:
summary: get job logs
operationId: getJob logs
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
responses:
"200":
description: job details
content:
text/plain:
schema:
type: string
/w/{workspace}/jobs_u/getupdate/{id}:
get:
summary: get job updates
+17
View File
@@ -120,6 +120,7 @@ pub fn global_service() -> Router {
get(get_suspended_job_flow),
)
.route("/get/:id", get(get_job))
.route("/get_logs/:id", get(get_job_logs))
.route("/completed/get/:id", get(get_completed_job))
.route("/completed/get_result/:id", get(get_completed_job_result))
.route(
@@ -276,6 +277,22 @@ async fn get_job(
Ok(Json(job))
}
async fn get_job_logs(
Extension(db): Extension<DB>,
Path((w_id, id)): Path<(String, Uuid)>,
) -> error::Result<String> {
let text = sqlx::query_scalar!(
"SELECT logs FROM completed_job WHERE id = $1 AND workspace_id = $2",
id,
w_id
)
.fetch_optional(&db)
.await?
.flatten();
let text = not_found_if_none(text, "Job Logs", id.to_string())?;
Ok(text)
}
pub async fn get_job_by_id<'c>(
mut tx: Transaction<'c, Postgres>,
w_id: &str,
@@ -9,6 +9,7 @@
export let noBorder = false
export let loading
export let filename: string | undefined = undefined
export let jobId: string | undefined = undefined
</script>
<div
@@ -26,6 +27,6 @@
{/if}
</div>
<div class="overflow-auto {col ? '' : 'max-h-80'} h-full relative">
<LogViewer content={logs ?? ''} isLoading={false} />
<LogViewer content={logs ?? ''} {jobId} isLoading={false} />
</div>
</div>
@@ -218,6 +218,7 @@
{#if `result` in job}
<div class="w-full h-full">
<FlowJobResult
jobId={job?.id}
loading={job['running'] == true}
result={job.result}
logs={job.logs ?? ''}
@@ -460,6 +461,7 @@
{@const node = localFlowModuleStates[selectedNode]}
{#if selectedNode == 'end'}
<FlowJobResult
jobId={job?.id}
filename={job.id}
loading={job['running']}
noBorder
@@ -498,6 +500,7 @@
</div>
<FlowJobResult
jobId={job?.id}
loading={job['running'] == true}
noBorder
col
+1 -1
View File
@@ -6,7 +6,7 @@
import Portal from 'svelte-portal'
import { Highlight } from 'svelte-highlight'
import { copyToClipboard } from '$lib/utils'
import { json, r } from 'svelte-highlight/languages'
import { json } from 'svelte-highlight/languages'
export let args: any
export let tableClass = ''
+15 -2
View File
@@ -1,12 +1,15 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { Drawer, DrawerContent } from './common'
import { ClipboardCopy, Download, Loader2 } from 'lucide-svelte'
import { Button, Drawer, DrawerContent } from './common'
import { copyToClipboard } from '$lib/utils'
import { workspaceStore } from '$lib/stores'
export let content: string | undefined
export let isLoading: boolean
export let duration: number | undefined = undefined
export let mem: number | undefined = undefined
export let wrapperClass = ''
export let jobId: string | undefined = undefined
let scroll = true
let div: HTMLElement | null = null
@@ -26,6 +29,16 @@
<Drawer bind:this={logViewer} size="900px">
<DrawerContent title="Expanded Logs" on:close={logViewer.closeDrawer}>
<svelte:fragment slot="actions">
<a
class="text-sm text-gray-600 mr-2 inline-flex gap-2 items-center py-2 px-2 hover:bg-gray-100 rounded-lg"
download="windmill-logs.json"
href="/api/w/{$workspaceStore}/jobs_u/get_logs/{jobId}">Download <Download size={14} /></a
>
<Button on:click={() => copyToClipboard(content)} color="light" size="xs">
<div class="flex gap-2 items-center">Copy to clipboard <ClipboardCopy /> </div>
</Button>
</svelte:fragment>
<div>
<pre class="bg-gray-50 text-xs w-full p-2"
>{#if content}{content}{:else if isLoading}Waiting for job to start...{:else}No logs are available yet{/if}</pre
@@ -102,6 +102,7 @@
<Splitpanes horizontal>
<Pane size={50} minSize={10}>
<LogViewer
jobId={testJob?.id}
duration={testJob?.['duration_ms']}
mem={testJob?.['mem_peak']}
content={testJob?.logs}
@@ -62,6 +62,7 @@
)}
>
<LogViewer
jobId={testJob?.id}
duration={testJob?.['duration_ms']}
mem={testJob?.['mem_peak']}
content={testJob?.logs}
@@ -9,9 +9,15 @@
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
import { writable } from 'svelte/store'
import { createSvelteTable, flexRender, type TableOptions } from '@tanstack/svelte-table'
import {
createSvelteTable,
flexRender,
type HeaderGroup,
type Table,
type TableOptions
} from '@tanstack/svelte-table'
import AppButton from '../../buttons/AppButton.svelte'
import { classNames, isObject } from '$lib/utils'
import { classNames, isObject, sendUserToast } from '$lib/utils'
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
import AppTableFooter from './AppTableFooter.svelte'
import { tableOptions } from './tableOptions'
@@ -200,6 +206,16 @@
}
}
}
function getHeaderGroups<T>(table: Table<T>): Array<HeaderGroup<T>> {
try {
return table.getHeaderGroups()
} catch (e) {
sendUserToast("Couldn't render table header groups: " + e, true)
console.error(e)
return []
}
}
</script>
{#each Object.keys(components['tablecomponent'].initialData.configuration) as key (key)}
@@ -241,7 +257,7 @@
)}
style={css?.tableHeader?.style ?? ''}
>
{#each $table.getHeaderGroups() as headerGroup}
{#each getHeaderGroups($table) as headerGroup}
<tr class="divide-x">
{#each headerGroup.headers as header}
{#if header?.column?.columnDef?.header}
@@ -632,7 +632,7 @@
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview'}
<Splitpanes horizontal class="grow border w-full">
<Pane size={50} minSize={10}>
<LogViewer content={job?.logs} isLoading={testIsLoading} />
<LogViewer jobId={job?.id} content={job?.logs} isLoading={testIsLoading} />
</Pane>
<Pane size={50} minSize={10} class="text-sm text-gray-600">
{#if job != undefined && 'result' in job && job.result != undefined}
@@ -124,6 +124,7 @@
{:else if job && `running` in job ? job.running : false}
<div class="text-sm font-semibold text-gray-600 mb-1"> Job is still running </div>
<LogViewer
jobId={job?.id}
duration={job?.['duration_ms']}
mem={job?.['mem_peak']}
content={job?.logs}
@@ -78,6 +78,7 @@
<Splitpanes horizontal>
<Pane class="relative">
<LogViewer
jobId={previewJob?.id}
duration={previewJob?.['duration_ms']}
mem={previewJob?.['mem_peak']}
content={previewJob?.logs}
@@ -346,6 +346,7 @@
{#if viewTab == 'logs'}
<div class="w-full">
<LogViewer
jobId={job.id}
duration={job?.['duration_ms']}
mem={job?.['mem_peak']}
isLoading={!(job && 'logs' in job && job.logs)}