mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
feat: move tag management to new job view header
This commit is contained in:
parent
704620baff
commit
b3ed2afebe
@ -233,7 +233,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row class="mb-0 mb-xxl-2">
|
<Row class="mb-0 mb-xxl-2">
|
||||||
<!-- Column 1: Job Info, Concurrent Jobs, Admin Message if found-->
|
<!-- Column 1: Job Info, Job Tags, Concurrent Jobs, Admin Message if found-->
|
||||||
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
|
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
@ -245,6 +245,11 @@
|
|||||||
<JobInfo job={$initq.data.job} {jobTags} />
|
<JobInfo job={$initq.data.job} {jobTags} />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
<TabPane tabId="job-tags" tab="Job Tags">
|
||||||
|
<CardBody>
|
||||||
|
<TagManagement job={$initq.data.job} {username} {authlevel} {roles} bind:jobTags renderModal={false}/>
|
||||||
|
</CardBody>
|
||||||
|
</TabPane>
|
||||||
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
|
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
|
||||||
<TabPane tabId="shared-jobs">
|
<TabPane tabId="shared-jobs">
|
||||||
<span slot="tab">
|
<span slot="tab">
|
||||||
@ -321,15 +326,10 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<Row class="mb-2">
|
<Row class="mb-2">
|
||||||
<Col xs="auto">
|
|
||||||
{#if $initq.data}
|
|
||||||
<TagManagement job={$initq.data.job} {username} {authlevel} {roles} bind:jobTags />
|
|
||||||
{/if}
|
|
||||||
</Col>
|
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
<Button outline on:click={() => (isMetricsSelectionOpen = true)}>
|
<Button outline on:click={() => (isMetricsSelectionOpen = true)}>
|
||||||
<Icon name="graph-up" /> Metrics
|
<Icon name="graph-up" /> Select Metrics
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -208,7 +208,7 @@
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
placement="right"
|
placement="right"
|
||||||
offset={[0, 20]}>{fpd.message}</Tooltip
|
>{fpd.message}</Tooltip
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
||||||
@ -246,7 +246,7 @@
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
placement="right"
|
placement="right"
|
||||||
offset={[0, 20]}>{fpd.message}</Tooltip
|
>{fpd.message}</Tooltip
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -265,7 +265,7 @@
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
placement="right"
|
placement="right"
|
||||||
offset={[0, 20]}>{fpd.message}</Tooltip
|
>{fpd.message}</Tooltip
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
||||||
@ -303,7 +303,7 @@
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
placement="right"
|
placement="right"
|
||||||
offset={[0, 20]}>{fpd.message}</Tooltip
|
>{fpd.message}</Tooltip
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -89,7 +89,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<Button outline on:click={() => (isMetricSelectionOpen = true)}>
|
<Button outline on:click={() => (isMetricSelectionOpen = true)}>
|
||||||
Metrics
|
<Icon name="graph-up" /> Select Metrics
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
{#each selectedMetrics as metric}
|
{#each selectedMetrics as metric}
|
||||||
|
@ -7,21 +7,28 @@
|
|||||||
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
||||||
- `authlevel Number`: The current users authentication level
|
- `authlevel Number`: The current users authentication level
|
||||||
- `roles [Number]`: Enum containing available roles
|
- `roles [Number]`: Enum containing available roles
|
||||||
|
- `renderModal Bool?`: If component is rendered as bootstrap modal button [Default: true]
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||||
import {
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
Icon,
|
Icon,
|
||||||
Button,
|
Button,
|
||||||
|
ListGroup,
|
||||||
ListGroupItem,
|
ListGroupItem,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupText,
|
||||||
Spinner,
|
Spinner,
|
||||||
Modal,
|
Modal,
|
||||||
Input,
|
|
||||||
ModalBody,
|
ModalBody,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
Alert,
|
Alert,
|
||||||
|
Tooltip,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { fuzzySearchTags } from "../generic/utils.js";
|
import { fuzzySearchTags } from "../generic/utils.js";
|
||||||
import Tag from "../generic/helper/Tag.svelte";
|
import Tag from "../generic/helper/Tag.svelte";
|
||||||
@ -31,6 +38,7 @@
|
|||||||
export let username;
|
export let username;
|
||||||
export let authlevel;
|
export let authlevel;
|
||||||
export let roles;
|
export let roles;
|
||||||
|
export let renderModal = true;
|
||||||
|
|
||||||
let allTags = getContext("tags"),
|
let allTags = getContext("tags"),
|
||||||
initialized = getContext("initialized");
|
initialized = getContext("initialized");
|
||||||
@ -40,6 +48,7 @@
|
|||||||
let filterTerm = "";
|
let filterTerm = "";
|
||||||
let pendingChange = false;
|
let pendingChange = false;
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
|
const isAdmin = (roles && authlevel >= roles.admin);
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
@ -94,8 +103,9 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let allTagsFiltered; // $initialized is in there because when it becomes true, allTags is initailzed.
|
|
||||||
$: allTagsFiltered = ($initialized, fuzzySearchTags(filterTerm, allTags));
|
$: allTagsFiltered = ($initialized, fuzzySearchTags(filterTerm, allTags));
|
||||||
|
$: usedTagsFiltered = matchJobTags(jobTags, allTagsFiltered, 'used');
|
||||||
|
$: unusedTagsFiltered = matchJobTags(jobTags, allTagsFiltered, 'unused');
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
newTagType = "";
|
newTagType = "";
|
||||||
@ -107,6 +117,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchJobTags(tags, availableTags, type) {
|
||||||
|
const jobTagIds = tags.map((t) => t.id)
|
||||||
|
if (type == 'used') {
|
||||||
|
return availableTags.filter((at) => jobTagIds.includes(at.id))
|
||||||
|
} else if (type == 'unused') {
|
||||||
|
return availableTags.filter((at) => !jobTagIds.includes(at.id))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
function isNewTag(type, name) {
|
function isNewTag(type, name) {
|
||||||
for (let tag of allTagsFiltered)
|
for (let tag of allTagsFiltered)
|
||||||
if (tag.type == type && tag.name == name) return false;
|
if (tag.type == type && tag.name == name) return false;
|
||||||
@ -155,6 +175,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if renderModal}
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Manage Tags
|
Manage Tags
|
||||||
@ -172,13 +193,11 @@
|
|||||||
bind:value={filterTerm}
|
bind:value={filterTerm}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<Alert color="info">
|
<Alert color="info">
|
||||||
Search using "<code>type: name</code>". If no tag matches your search, a
|
Search using "<code>type: name</code>". If no tag matches your search, a
|
||||||
button for creating a new one will appear.
|
button for creating a new one will appear.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<br />
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{#each allTagsFiltered as tag}
|
{#each allTagsFiltered as tag}
|
||||||
<ListGroupItem>
|
<ListGroupItem>
|
||||||
@ -252,6 +271,114 @@
|
|||||||
<Button outline on:click={() => (isOpen = true)}>
|
<Button outline on:click={() => (isOpen = true)}>
|
||||||
Manage Tags <Icon name="tags" />
|
Manage Tags <Icon name="tags" />
|
||||||
</Button>
|
</Button>
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<InputGroup class="mb-3">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Tags"
|
||||||
|
bind:value={filterTerm}
|
||||||
|
/>
|
||||||
|
<InputGroupText id={`tag-management-info`} style="cursor:help; font-size:larger;align-content:center;">
|
||||||
|
<Icon name=info-circle/>
|
||||||
|
</InputGroupText>
|
||||||
|
<Tooltip
|
||||||
|
target={`tag-management-info`}
|
||||||
|
placement="right">
|
||||||
|
Search using "type: name". If no tag matches your search, a
|
||||||
|
button for creating a new one will appear.
|
||||||
|
</Tooltip>
|
||||||
|
</InputGroup>
|
||||||
|
|
||||||
|
{#if usedTagsFiltered.length > 0}
|
||||||
|
<ListGroup class="mb-3">
|
||||||
|
{#each usedTagsFiltered as utag}
|
||||||
|
<ListGroupItem color="primary">
|
||||||
|
<Tag tag={utag} />
|
||||||
|
|
||||||
|
<span style="float: right;">
|
||||||
|
{#if pendingChange === utag.id}
|
||||||
|
<Spinner size="sm" secondary />
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="danger"
|
||||||
|
on:click={() => removeTagFromJob(utag)}
|
||||||
|
>
|
||||||
|
<Icon name="x" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</ListGroupItem>
|
||||||
|
{/each}
|
||||||
|
</ListGroup>
|
||||||
|
{:else if filterTerm !== ""}
|
||||||
|
<ListGroup class="mb-3">
|
||||||
|
<ListGroupItem disabled>
|
||||||
|
<i>No used tags matching</i>
|
||||||
|
</ListGroupItem>
|
||||||
|
</ListGroup>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if unusedTagsFiltered.length > 0}
|
||||||
|
<ListGroup class="mb-3">
|
||||||
|
{#each unusedTagsFiltered as uutag}
|
||||||
|
<ListGroupItem color="dark">
|
||||||
|
<Tag tag={uutag} />
|
||||||
|
|
||||||
|
<span style="float: right;">
|
||||||
|
{#if pendingChange === uutag.id}
|
||||||
|
<Spinner size="sm" secondary />
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="success"
|
||||||
|
on:click={() => addTagToJob(uutag)}
|
||||||
|
>
|
||||||
|
<Icon name="plus" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</ListGroupItem>
|
||||||
|
{/each}
|
||||||
|
</ListGroup>
|
||||||
|
{:else if filterTerm !== ""}
|
||||||
|
<ListGroup class="mb-3">
|
||||||
|
<ListGroupItem disabled>
|
||||||
|
<i>No unused tags matching</i>
|
||||||
|
</ListGroupItem>
|
||||||
|
</ListGroup>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if newTagType && newTagName && isNewTag(newTagType, newTagName)}
|
||||||
|
<Row>
|
||||||
|
<Col xs={isAdmin ? 7 : 12} md={12} lg={isAdmin ? 7 : 12} xl={12} xxl={isAdmin ? 7 : 12} class="mb-2">
|
||||||
|
<Button
|
||||||
|
outline
|
||||||
|
style="width:100%;"
|
||||||
|
color="success"
|
||||||
|
on:click={(e) => (
|
||||||
|
e.preventDefault(), createTag(newTagType, newTagName, newTagScope)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Add new tag:
|
||||||
|
<Tag tag={{ type: newTagType, name: newTagName, scope: newTagScope }} clickable={false}/>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
{#if isAdmin}
|
||||||
|
<Col xs={5} md={12} lg={5} xl={12} xxl={5} class="mb-2" style="align-content:center;">
|
||||||
|
<Input type="select" bind:value={newTagScope}>
|
||||||
|
<option value={username}>Scope: Private</option>
|
||||||
|
<option value={"global"}>Scope: Global</option>
|
||||||
|
<option value={"admin"}>Scope: Admin</option>
|
||||||
|
</Input>
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
</Row>
|
||||||
|
{:else if allTagsFiltered.length == 0}
|
||||||
|
<Alert color="info">Search Term is not a valid Tag (<code>type: name</code>)</Alert>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
ul.list-group {
|
ul.list-group {
|
||||||
|
Loading…
Reference in New Issue
Block a user