Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/frontend/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,9 @@ legend {
.no-link-style a {
color: inherit !important;
text-decoration: none !important;
}

.font-mono {
font-family: monospace,Courier, 'Courier New';
letter-spacing:.4px;
}
178 changes: 118 additions & 60 deletions src/frontend/src/components/AssignPluginsDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,103 +14,161 @@
@add="(added) => addPlugin(added.value)"
@remove="(removed) => removePlugin(removed.value)"
>
<template v-slot:before>
<div class="field-label">Plugins:</div>
</template>
<!-- Removed to reduce clutter -->
<!-- <template v-slot:before>
<div class="field-label">Attached Plugins:</div>
</template> -->

<template v-slot:selected>
<div>
<div class="q-pa-xs full-width">
<div
v-for="(plugin, i) in selectedPlugins"
:key="plugin.id"
:class="i > 0 ? 'q-mt-xs' : ''"
class="q-mt-sm"
>
<div
class="row items-center no-wrap q-pa-none shadow-1"
style="
border-radius: 4px;
border: 1px solid #eeeeee;
width: fit-content;
max-width: 100%;
"
>
<q-chip
removable
color="secondary"
text-color="white"
class="q-ml-xs "
@remove="selectedPlugins.splice(i, 1); removePlugin(plugin)"
:color="$q.dark.isActive ? pluginStyle.darkColor : pluginStyle.color"
:icon="pluginStyle.icon"
size="sm"
outline
square
class="text-weight-bold q-py-sm q-ma-none no-border"
:class="$q.dark.isActive ? 'bg-grey-10' : 'bg-grey-1'"
@remove="
selectedPlugins.splice(i, 1);
removePlugin(plugin);
"
>
{{ plugin.name }}
<q-badge
v-if="!plugin.latestSnapshot"
color="red"
label="outdated"
rounded
class="q-ml-xs"
/>
<span
class="font-mono ellipsis q-pr-md"
style="font-size: 12px; font-weight: 500"
>
{{ plugin.name }}
</span>

<q-tooltip>Plugin ID: {{ plugin.id }}</q-tooltip>
</q-chip>
<q-btn

<div
v-if="!plugin.latestSnapshot"
round
color="red"
icon="sync"
size="sm"
@click.stop="syncPlugin(plugin.id, i)"
class="row items-center no-wrap q-pr-sm"
>
<q-tooltip>
Sync to latest version of plugin
</q-tooltip>
</q-btn>
<div
style="
height: 14px;
width: 1px;
background-color: #eee;
margin: 0 4px;
"
></div>

<q-icon name="warning" color="orange" size="xs">
<q-tooltip>Plugin is out of date</q-tooltip>
</q-icon>

<q-btn
flat
round
size="xs"
color="red"
icon="sync"
class="q-ml-xs"
@click.stop="syncPlugin(plugin.id, i)"
>
<q-tooltip>Sync to latest version</q-tooltip>
</q-btn>
</div>
</div>
</div>
</div>
</template>
</q-select>
</template>

<script setup>
import { ref, watch } from 'vue'
import * as api from '@/services/dataApi'
import { ref, watch, computed } from "vue";
import * as api from "@/services/dataApi";
import { getConceptStyle } from "@/constants/tableStyles";
import * as notify from "../notify";

const selectedPlugins = defineModel('selectedPlugins')
const originalSelectedPluginIds = ref([])
const pluginStyle = computed(() => getConceptStyle("plugin"));
const selectedPlugins = defineModel("selectedPlugins");
const pluginIDsToUpdate = defineModel("pluginIDsToUpdate");
const pluginIDsToRemove = defineModel("pluginIDsToRemove");
const originalSelectedPluginIds = ref([]);
const pluginOptions = ref([]);
import { useQuasar } from "quasar";

watch(selectedPlugins, (newVal) => {
originalSelectedPluginIds.value = newVal.map(p => p.id)
const $q = useQuasar();
watch(
selectedPlugins,
(newVal) => {
if (newVal?.length > 0) {
originalSelectedPluginIds.value = newVal.map((p) => p.id);
}
},
{ once: true }
)
);

const pluginIDsToUpdate = defineModel('pluginIDsToUpdate')
const pluginIDsToRemove = defineModel('pluginIDsToRemove')

const pluginOptions = ref([])

async function getPlugins(val = '', update) {
async function getPlugins(val = "", update) {
update(async () => {
try {
const res = await api.getData('plugins', {
const res = await api.getData("plugins", {
search: val,
rowsPerPage: 0, // get all
index: 0
})
pluginOptions.value = res.data.data
} catch(err) {
notify.error(err.response.data.message)
}
})
rowsPerPage: 0,
index: 0,
});
pluginOptions.value = res.data.data;
} catch (err) {
console.warn(err);
const msg = err.response?.data?.message || "Failed to load plugins";
notify.error(msg);
}
});
}

async function syncPlugin(pluginId, index) {
try {
const res = await api.getItem('plugins', pluginId)
selectedPlugins.value.splice(index, 1, res.data)
pluginIDsToUpdate.value.push(pluginId)
} catch(err) {
console.warn(err)
const res = await api.getItem("plugins", pluginId);
selectedPlugins.value.splice(index, 1, res.data);
pluginIDsToUpdate.value.push(pluginId);
} catch (err) {
console.warn(err);
}
}

function addPlugin(plugin) {
pluginIDsToUpdate.value.push(plugin.id)
pluginIDsToRemove.value = pluginIDsToRemove.value.filter((id) => id !== plugin.id)
pluginIDsToUpdate.value.push(plugin.id);
pluginIDsToRemove.value = pluginIDsToRemove.value.filter(
(id) => id !== plugin.id,
);
}

function removePlugin(plugin) {
if(originalSelectedPluginIds.value.includes(plugin.id)) {
pluginIDsToRemove.value.push(plugin.id)
if (originalSelectedPluginIds.value.includes(plugin.id)) {
pluginIDsToRemove.value.push(plugin.id);
}
pluginIDsToUpdate.value = pluginIDsToUpdate.value.filter((id) => id !== plugin.id)
pluginIDsToUpdate.value = pluginIDsToUpdate.value.filter(
(id) => id !== plugin.id,
);
}
</script>

</script>
<style scoped>
.field-label {
font-weight: 500;
font-size: 14px;
color: rgba(0, 0, 0, 0.6);
margin-right: 8px;
}
</style>
104 changes: 75 additions & 29 deletions src/frontend/src/components/JobArtifactsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,107 @@
:hideCreateBtn="true"
:loading="isLoading"
>
<template #body-cell-taskName="props">
{{ props.row.task.name }}
</template>
<template #body-cell-taskOutputParams="props">
<q-chip
v-for="param in props.row.task.outputParams"
color="purple"
text-color="white"
dense
>
{{ param.name }}: {{ param.parameterType.name }}
</q-chip>
</template>
<template #body-cell-download="props">
<q-btn
:href="props.row.fileUrl"
:download="`artifact-${props.row?.id}`"
color="primary"
round
round dense flat
icon="download"
size="sm"
size="md"
@click.stop
/>
>
<q-tooltip>Download Artifact</q-tooltip>
</q-btn>
</template>
</TableComponent>
</template>

<script setup>
import TableComponent from '@/components/TableComponent.vue'
import { ref, onMounted } from 'vue'
import * as api from '@/services/dataApi'
import { useRouter, useRoute } from 'vue-router'
import * as api from '@/services/dataApi'
import * as notify from '../notify'
import TableComponent from '@/components/table/TableComponent.vue'

const openWindow = window
const router = useRouter()
const route = useRoute()

const props = defineProps(['artifactIds'])
const selected = ref([])
const artifacts = ref([])
const isLoading = ref(false)

const columns = [
{
name: 'id',
label: 'Artifact ID',
field: 'id',
align: 'left',
styleType: 'icon-badge',
conceptType: 'artifact',
includeIcon: true,
size: 'md',
uppercase: false,
formatLabel: 'Artifact #{label}',
clickable: true,
field: (row) => {
if (!row.task) return "-";
return {
name: row.id,
to: `/artifacts/${row.id}`
};
},
},
{
name: 'description',
label: 'Description',
field: 'description',
align: 'left',
styleType: 'long-text',
maxWidth: '250px'
},
{
name: 'taskName',
label: 'Task Name',
align: 'left',
styleType: 'icon-badge',
conceptType: 'task',
chipType: 'outline',
uppercase: false,
field: (row) => {
if (!row.task) return "-";
return {
name: row.task.name,
to: `/plugins/${row.task.pluginResourceId}/files/${row.task.pluginFileResourceId}`
};
},
},
{
name: 'taskOutputParams',
label: 'Task Output Params',
// Pass the array directly to the component
field: row => row.task?.outputParams || [],
align: 'left',
styleType: 'parameter-list',
parameterType: 'output',
style: 'min-width: 250px'
},
{
name: 'download',
label: 'Download',
align: 'center',
headerStyle: 'width: 50px'
},
]

onMounted(() => {
getArtifacts()
if (props.artifactIds && props.artifactIds.length) {
getArtifacts()
}
})

const artifacts = ref([])
const isLoading = ref(false)

async function getArtifacts() {
isLoading.value = true
const minLoadTimePromise = new Promise(resolve => setTimeout(resolve, 300))
Expand All @@ -85,11 +138,4 @@ async function getArtifacts() {
}


const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
{ name: 'description', label: 'Description', field: 'description', align: 'left', sortable: true },
{ name: 'taskName', label: 'Task Name', align: 'left' },
{ name: 'taskOutputParams', label: 'Task Output Params', align: 'left' },
{ name: 'download', label: 'Download', align: 'center' },
]
</script>
Loading
Loading