Added jobRuns on the job page
Some checks failed
Push image to registry / build-image (push) Failing after 3m47s
Some checks failed
Push image to registry / build-image (push) Failing after 3m47s
This commit is contained in:
@ -21,7 +21,7 @@ class JobController extends Controller
|
||||
public function show($jobId, Request $request)
|
||||
{
|
||||
return Inertia::render('Job', [
|
||||
'job' => Job::where('id', $jobId)->with('jobInfos')->first(),
|
||||
'job' => Job::where('id', $jobId)->with('jobInfos', 'jobRuns')->first(),
|
||||
'error' => $request->input('error'),
|
||||
]);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "default",
|
||||
"typescript": true,
|
||||
"tsConfigPath": "./tsconfig.json",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "resources/css/app.css",
|
||||
@ -10,9 +9,12 @@
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"framework": "laravel",
|
||||
"aliases": {
|
||||
"components": "@/Components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
"composables": "@/composables",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/Components/ui",
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
77
package-lock.json
generated
77
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"lucide-react": "^0.474.0",
|
||||
"lucide-vue-next": "^0.474.0",
|
||||
"radix-vue": "^1.9.13",
|
||||
"reka-ui": "^2.1.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
@ -21,8 +22,10 @@
|
||||
"autoprefixer": "^10.4.12",
|
||||
"axios": "^1.7.4",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-echo": "^2.0.2",
|
||||
"laravel-vite-plugin": "^1.2.0",
|
||||
"postcss": "^8.4.31",
|
||||
"pusher-js": "^8.4.0",
|
||||
"sass-embedded": "^1.83.4",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"typescript": "^5.6.3",
|
||||
@ -956,20 +959,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz",
|
||||
"integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==",
|
||||
"version": "3.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.4.tgz",
|
||||
"integrity": "sha512-fNGO9fjjSLns87tlcto106enQQLycCKR4DPNpgq3djP5IdcPFdPAmaKjsgzIeRhH7hWrELgW12hYnRthS5kLUw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/vue-virtual": {
|
||||
"version": "3.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.11.3.tgz",
|
||||
"integrity": "sha512-BVZ00i5XBucetRj2doVd32jOPtJthvZSVJvx9GL4gSQsyngliSCtzlP1Op7TFrEtmebRKT8QUQE1tRhOQzWecQ==",
|
||||
"version": "3.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.4.tgz",
|
||||
"integrity": "sha512-1fPrd3hE1SS4R/9JbX1AlzueY4duCK7ixuLcMW5GMnk9N6WbLo9MioNKiv22V+UaXKOLNy8tLdzT8NYerOFTOQ==",
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.11.3"
|
||||
"@tanstack/virtual-core": "3.13.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@ -2239,6 +2242,15 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-echo": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.0.2.tgz",
|
||||
"integrity": "sha512-Ciai6hA7r35MFqNRb8G034cvm9WiveSTFQQKRGJhWtZGbng7C8BBa5QvqDxk/Mw5GeJ+q19jrEwQhf7r1b1lcg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz",
|
||||
@ -2487,6 +2499,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz",
|
||||
"integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@ -2700,6 +2717,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pusher-js": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
|
||||
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tweetnacl": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
@ -2847,6 +2873,37 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.1.0.tgz",
|
||||
"integrity": "sha512-w4kEDEyXhIqv4QeFJeiuBc4mQP37hH/UTRpEb9dMbPdR49JG5TcV/s0+ntNRONUUW4LDLX7E1ZPcwBw5hnu0yw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@floating-ui/vue": "^1.1.6",
|
||||
"@internationalized/date": "^3.5.0",
|
||||
"@internationalized/number": "^3.5.0",
|
||||
"@tanstack/vue-virtual": "^3.12.0",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"@vueuse/shared": "^12.5.0",
|
||||
"aria-hidden": "^1.2.4",
|
||||
"defu": "^6.1.4",
|
||||
"ohash": "^1.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">= 3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui/node_modules/@vueuse/shared": {
|
||||
"version": "12.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
|
||||
"integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -3697,6 +3754,12 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
|
@ -30,6 +30,7 @@
|
||||
"lucide-react": "^0.474.0",
|
||||
"lucide-vue-next": "^0.474.0",
|
||||
"radix-vue": "^1.9.13",
|
||||
"reka-ui": "^2.1.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import Separator from "@/Components/ui/separator/Separator.vue";
|
||||
import { JobRunArtifact } from "@/types/Jobs/job";
|
||||
|
||||
defineProps<{
|
||||
jobRun: JobRunArtifact;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="artifact in jobRun.artifacts" :key="artifact.id">
|
||||
<p>{{ artifact.name }}</p>
|
||||
<p class="italic">{{ artifact.content }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
ul {
|
||||
list-style-type: circle;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
</style>
|
28
resources/js/Components/Layout/Job/JobRuns/JobRunItem.vue
Normal file
28
resources/js/Components/Layout/Job/JobRuns/JobRunItem.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import AccordionContent from "@/Components/ui/accordion/AccordionContent.vue";
|
||||
import AccordionItem from "@/Components/ui/accordion/AccordionItem.vue";
|
||||
import AccordionTrigger from "@/Components/ui/accordion/AccordionTrigger.vue";
|
||||
import { JobRunArtifact } from "@/types/Jobs/job";
|
||||
import JobRunArtifacts from "./JobRunArtifacts.vue";
|
||||
|
||||
defineProps<{
|
||||
jobRun: JobRunArtifact;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionItem :value="''+jobRun.id" :class="[jobRun.success ? 'bg-green-100' : 'bg-red-200', 'first:rounded-t last:rounded-b', 'px-3']">
|
||||
<AccordionTrigger>
|
||||
{{ new Date(Date.parse(jobRun.created_at)).toLocaleTimeString(undefined, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<JobRunArtifacts :jobRun="jobRun" />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</template>
|
28
resources/js/Components/Layout/Job/JobRuns/JobRuns.vue
Normal file
28
resources/js/Components/Layout/Job/JobRuns/JobRuns.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { Job, JobRunArtifact } from "@/types/Jobs/job";
|
||||
import JobRunItem from "./JobRunItem.vue";
|
||||
import Accordion from "@/Components/ui/accordion/Accordion.vue";
|
||||
import ScrollArea from "@/Components/ui/scroll-area/ScrollArea.vue";
|
||||
|
||||
defineProps<{
|
||||
job: Job;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Ancien jobs</h2>
|
||||
<ScrollArea class="min-h-[300px] max-h-[20vh] overflow-auto pr-2">
|
||||
<Accordion type="multiple" collapsible>
|
||||
<JobRunItem
|
||||
:jobRun="jobRun"
|
||||
v-for="jobRun in job.job_runs.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.created_at).getTime() -
|
||||
new Date(a.created_at).getTime()
|
||||
);
|
||||
})"
|
||||
:key="jobRun.id"
|
||||
/>
|
||||
</Accordion>
|
||||
</ScrollArea>
|
||||
</template>
|
19
resources/js/Components/ui/accordion/Accordion.vue
Normal file
19
resources/js/Components/ui/accordion/Accordion.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AccordionRoot,
|
||||
type AccordionRootEmits,
|
||||
type AccordionRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui'
|
||||
|
||||
const props = defineProps<AccordionRootProps>()
|
||||
const emits = defineEmits<AccordionRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</AccordionRoot>
|
||||
</template>
|
24
resources/js/Components/ui/accordion/AccordionContent.vue
Normal file
24
resources/js/Components/ui/accordion/AccordionContent.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { AccordionContent, type AccordionContentProps } from 'reka-ui'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionContent
|
||||
v-bind="delegatedProps"
|
||||
class="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
>
|
||||
<div :class="cn('pb-4 pt-0', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</template>
|
24
resources/js/Components/ui/accordion/AccordionItem.vue
Normal file
24
resources/js/Components/ui/accordion/AccordionItem.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'reka-ui'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionItem
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('border-b', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</AccordionItem>
|
||||
</template>
|
39
resources/js/Components/ui/accordion/AccordionTrigger.vue
Normal file
39
resources/js/Components/ui/accordion/AccordionTrigger.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import {
|
||||
AccordionHeader,
|
||||
AccordionTrigger,
|
||||
type AccordionTriggerProps,
|
||||
} from 'reka-ui'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionHeader class="flex">
|
||||
<AccordionTrigger
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<slot name="icon">
|
||||
<ChevronDown
|
||||
class="h-4 w-4 shrink-0 transition-transform duration-200"
|
||||
/>
|
||||
</slot>
|
||||
</AccordionTrigger>
|
||||
</AccordionHeader>
|
||||
</template>
|
4
resources/js/Components/ui/accordion/index.ts
Normal file
4
resources/js/Components/ui/accordion/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as Accordion } from './Accordion.vue'
|
||||
export { default as AccordionContent } from './AccordionContent.vue'
|
||||
export { default as AccordionItem } from './AccordionItem.vue'
|
||||
export { default as AccordionTrigger } from './AccordionTrigger.vue'
|
35
resources/js/Components/ui/separator/Separator.vue
Normal file
35
resources/js/Components/ui/separator/Separator.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Separator, type SeparatorProps } from 'reka-ui'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<
|
||||
SeparatorProps & { class?: HTMLAttributes['class'], label?: string }
|
||||
>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Separator
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'shrink-0 bg-border relative',
|
||||
props.orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span
|
||||
v-if="props.label"
|
||||
:class="cn('text-xs text-muted-foreground bg-background absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex justify-center items-center',
|
||||
props.orientation === 'vertical' ? 'w-[1px] px-1 py-2' : 'h-[1px] py-1 px-2',
|
||||
)"
|
||||
>{{ props.label }}</span>
|
||||
</Separator>
|
||||
</template>
|
1
resources/js/Components/ui/separator/index.ts
Normal file
1
resources/js/Components/ui/separator/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Separator } from './Separator.vue'
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import JobForm from '../Components/Layout/Job/JobForm.vue'
|
||||
import JobCard from '../Components/Layout/Job/JobCard.vue'
|
||||
import JobRuns from '../Components/Layout/Job/JobRuns/JobRuns.vue';
|
||||
import { Job } from "@/types/Jobs/job";
|
||||
import { Head } from "@inertiajs/vue3";
|
||||
|
||||
@ -16,4 +17,6 @@ defineProps<{
|
||||
<JobCard :job="job" />
|
||||
|
||||
<JobForm :job="job" :error="error" />
|
||||
|
||||
<JobRuns :job="job" />
|
||||
</template>
|
||||
|
@ -1,8 +1,17 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
import type { Ref } from 'vue'
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
|
||||
ref.value
|
||||
= typeof updaterOrValue === 'function'
|
||||
? updaterOrValue(ref.value)
|
||||
: updaterOrValue
|
||||
}
|
||||
|
||||
export async function httpApi<T>(route: string): Promise<T> {
|
||||
|
14
resources/js/types/Jobs/job.d.ts
vendored
14
resources/js/types/Jobs/job.d.ts
vendored
@ -5,8 +5,9 @@ export type Job = {
|
||||
is_active: boolean;
|
||||
|
||||
job_infos: JobInfo[];
|
||||
job_runs: JobRunArtifact[];
|
||||
|
||||
created_at: Date;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export type JobInfo = {
|
||||
@ -26,16 +27,23 @@ export type JobInfo = {
|
||||
export type JobInfoType = {
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: Date;
|
||||
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export type JobRunArtifact = {
|
||||
jobId: number;
|
||||
id: number;
|
||||
job_id: number;
|
||||
artifacts: JobArtifact[];
|
||||
success: boolean;
|
||||
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export type JobArtifact = {
|
||||
id: number;
|
||||
name: string;
|
||||
content: string;
|
||||
|
||||
created_at: string;
|
||||
}
|
||||
|
Reference in New Issue
Block a user