mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-03 17:15:06 +01:00 
			
		
		
		
	feat: move tag management to new job view header
This commit is contained in:
		@@ -233,7 +233,7 @@
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<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">
 | 
			
		||||
    {#if $initq.error}
 | 
			
		||||
      <Card body color="danger">{$initq.error.message}</Card>
 | 
			
		||||
@@ -245,6 +245,11 @@
 | 
			
		||||
              <JobInfo job={$initq.data.job} {jobTags} />
 | 
			
		||||
            </CardBody>
 | 
			
		||||
          </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}
 | 
			
		||||
            <TabPane  tabId="shared-jobs">
 | 
			
		||||
              <span slot="tab">
 | 
			
		||||
@@ -321,15 +326,10 @@
 | 
			
		||||
<hr/>
 | 
			
		||||
 | 
			
		||||
<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">
 | 
			
		||||
    {#if $initq.data}
 | 
			
		||||
      <Button outline on:click={() => (isMetricsSelectionOpen = true)}>
 | 
			
		||||
        <Icon name="graph-up" /> Metrics
 | 
			
		||||
        <Icon name="graph-up" /> Select Metrics
 | 
			
		||||
      </Button>
 | 
			
		||||
    {/if}
 | 
			
		||||
  </Col>
 | 
			
		||||
 
 | 
			
		||||
@@ -208,7 +208,7 @@
 | 
			
		||||
          <Tooltip
 | 
			
		||||
            target={`footprint-${job.jobId}-${index}`}
 | 
			
		||||
            placement="right"
 | 
			
		||||
            offset={[0, 20]}>{fpd.message}</Tooltip
 | 
			
		||||
          >{fpd.message}</Tooltip
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
        <Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
 | 
			
		||||
@@ -246,7 +246,7 @@
 | 
			
		||||
        <Tooltip
 | 
			
		||||
          target={`footprint-${job.jobId}-${index}`}
 | 
			
		||||
          placement="right"
 | 
			
		||||
          offset={[0, 20]}>{fpd.message}</Tooltip
 | 
			
		||||
        >{fpd.message}</Tooltip
 | 
			
		||||
        >
 | 
			
		||||
      {/if}
 | 
			
		||||
    {/each}
 | 
			
		||||
 
 | 
			
		||||
@@ -265,7 +265,7 @@
 | 
			
		||||
              <Tooltip
 | 
			
		||||
                target={`footprint-${job.jobId}-${index}`}
 | 
			
		||||
                placement="right"
 | 
			
		||||
                offset={[0, 20]}>{fpd.message}</Tooltip
 | 
			
		||||
              >{fpd.message}</Tooltip
 | 
			
		||||
              >
 | 
			
		||||
            </div>
 | 
			
		||||
            <Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
 | 
			
		||||
@@ -303,7 +303,7 @@
 | 
			
		||||
            <Tooltip
 | 
			
		||||
              target={`footprint-${job.jobId}-${index}`}
 | 
			
		||||
              placement="right"
 | 
			
		||||
              offset={[0, 20]}>{fpd.message}</Tooltip
 | 
			
		||||
            >{fpd.message}</Tooltip
 | 
			
		||||
            >
 | 
			
		||||
          {/if}
 | 
			
		||||
        {/each}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@
 | 
			
		||||
    <tr>
 | 
			
		||||
      <th>
 | 
			
		||||
        <Button outline on:click={() => (isMetricSelectionOpen = true)}>
 | 
			
		||||
          Metrics
 | 
			
		||||
          <Icon name="graph-up" /> Select Metrics
 | 
			
		||||
        </Button>
 | 
			
		||||
      </th>
 | 
			
		||||
      {#each selectedMetrics as metric}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,21 +7,28 @@
 | 
			
		||||
    - `username String`: Empty string if auth. is disabled, otherwise the username as string
 | 
			
		||||
    - `authlevel Number`: The current users authentication level
 | 
			
		||||
    - `roles [Number]`: Enum containing available roles
 | 
			
		||||
    - `renderModal Bool?`: If component is rendered as bootstrap modal button [Default: true]
 | 
			
		||||
 -->
 | 
			
		||||
<script>
 | 
			
		||||
  import { getContext } from "svelte";
 | 
			
		||||
  import { gql, getContextClient, mutationStore } from "@urql/svelte";
 | 
			
		||||
  import {
 | 
			
		||||
    Row,
 | 
			
		||||
    Col,
 | 
			
		||||
    Icon,
 | 
			
		||||
    Button,
 | 
			
		||||
    ListGroup,
 | 
			
		||||
    ListGroupItem,
 | 
			
		||||
    Input,
 | 
			
		||||
    InputGroup,
 | 
			
		||||
    InputGroupText,
 | 
			
		||||
    Spinner,
 | 
			
		||||
    Modal,
 | 
			
		||||
    Input,
 | 
			
		||||
    ModalBody,
 | 
			
		||||
    ModalHeader,
 | 
			
		||||
    ModalFooter,
 | 
			
		||||
    Alert,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
  } from "@sveltestrap/sveltestrap";
 | 
			
		||||
  import { fuzzySearchTags } from "../generic/utils.js";
 | 
			
		||||
  import Tag from "../generic/helper/Tag.svelte";
 | 
			
		||||
@@ -31,6 +38,7 @@
 | 
			
		||||
  export let username;
 | 
			
		||||
  export let authlevel;
 | 
			
		||||
  export let roles;
 | 
			
		||||
  export let renderModal = true;
 | 
			
		||||
 | 
			
		||||
  let allTags = getContext("tags"),
 | 
			
		||||
    initialized = getContext("initialized");
 | 
			
		||||
@@ -40,6 +48,7 @@
 | 
			
		||||
  let filterTerm = "";
 | 
			
		||||
  let pendingChange = false;
 | 
			
		||||
  let isOpen = false;
 | 
			
		||||
  const isAdmin = (roles && authlevel >= roles.admin);
 | 
			
		||||
 | 
			
		||||
  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));
 | 
			
		||||
  $: usedTagsFiltered = matchJobTags(jobTags, allTagsFiltered, 'used');
 | 
			
		||||
  $: unusedTagsFiltered = matchJobTags(jobTags, allTagsFiltered, 'unused');
 | 
			
		||||
 | 
			
		||||
  $: {
 | 
			
		||||
    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) {
 | 
			
		||||
    for (let tag of allTagsFiltered)
 | 
			
		||||
      if (tag.type == type && tag.name == name) return false;
 | 
			
		||||
@@ -155,7 +175,8 @@
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
 | 
			
		||||
{#if renderModal}
 | 
			
		||||
  <Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
 | 
			
		||||
    <ModalHeader>
 | 
			
		||||
      Manage Tags
 | 
			
		||||
      {#if pendingChange !== false}
 | 
			
		||||
@@ -172,13 +193,11 @@
 | 
			
		||||
        bind:value={filterTerm}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
    <br />
 | 
			
		||||
 | 
			
		||||
      <Alert color="info">
 | 
			
		||||
        Search using "<code>type: name</code>". If no tag matches your search, a
 | 
			
		||||
        button for creating a new one will appear.
 | 
			
		||||
      </Alert>
 | 
			
		||||
 | 
			
		||||
      <br />
 | 
			
		||||
      <ul class="list-group">
 | 
			
		||||
        {#each allTagsFiltered as tag}
 | 
			
		||||
          <ListGroupItem>
 | 
			
		||||
@@ -247,11 +266,119 @@
 | 
			
		||||
    <ModalFooter>
 | 
			
		||||
      <Button color="primary" on:click={() => (isOpen = false)}>Close</Button>
 | 
			
		||||
    </ModalFooter>
 | 
			
		||||
</Modal>
 | 
			
		||||
  </Modal>
 | 
			
		||||
 | 
			
		||||
<Button outline on:click={() => (isOpen = true)}>
 | 
			
		||||
  <Button outline on:click={() => (isOpen = true)}>
 | 
			
		||||
    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>
 | 
			
		||||
  ul.list-group {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user