mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 08:04:25 +00:00
feat: download logs from backend
This commit is contained in:
@@ -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": [],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user