mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 09:35:07 +01:00 
			
		
		
		
	Add FileStashUrl type and Query methods***
***Add Control component and update Header component*** ***Add control.tmpl template*** ***Update rollup.config.mjs*** ***Update routes.go*** ***Add VerticalTab and LinuxUser components
This commit is contained in:
		@@ -52,7 +52,7 @@ const entrypoint = (name, path) => ({
 | 
			
		||||
        // we'll extract any component CSS out into
 | 
			
		||||
        // a separate file - better for performance
 | 
			
		||||
        css({ output: `${name}.css` }),
 | 
			
		||||
        !production && livereload('public')
 | 
			
		||||
        livereload('public')
 | 
			
		||||
    ],
 | 
			
		||||
    watch: {
 | 
			
		||||
        clearScreen: false
 | 
			
		||||
@@ -68,7 +68,7 @@ export default [
 | 
			
		||||
    entrypoint('systems', 'src/systems.entrypoint.js'),
 | 
			
		||||
    entrypoint('node', 'src/node.entrypoint.js'),
 | 
			
		||||
    entrypoint('analysis', 'src/analysis.entrypoint.js'),
 | 
			
		||||
    entrypoint('status', 'src/status.entrypoint.js'),
 | 
			
		||||
    entrypoint('control', 'src/control.entrypoint.js'),
 | 
			
		||||
    entrypoint('config', 'src/config.entrypoint.js'),
 | 
			
		||||
    entrypoint('partitions', 'src/partitions.entrypoint.js'),
 | 
			
		||||
    entrypoint('history', 'src/history.entrypoint.js')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										381
									
								
								web/frontend/src/Control.root.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								web/frontend/src/Control.root.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,381 @@
 | 
			
		||||
<script>
 | 
			
		||||
  import { getContext } from "svelte";
 | 
			
		||||
  import Refresher from "./joblist/Refresher.svelte";
 | 
			
		||||
  import Roofline from "./plots/Roofline.svelte";
 | 
			
		||||
  import Pie, { colors } from "./plots/Pie.svelte";
 | 
			
		||||
  import Histogram from "./plots/Histogram.svelte";
 | 
			
		||||
  import {
 | 
			
		||||
    Row,
 | 
			
		||||
    Col,
 | 
			
		||||
    Spinner,
 | 
			
		||||
    Card,
 | 
			
		||||
    CardHeader,
 | 
			
		||||
    CardTitle,
 | 
			
		||||
    CardBody,
 | 
			
		||||
    Table,
 | 
			
		||||
    Progress,
 | 
			
		||||
    Icon,
 | 
			
		||||
    Button,
 | 
			
		||||
    Modal,
 | 
			
		||||
    ModalHeader,
 | 
			
		||||
    ModalBody,
 | 
			
		||||
    ModalFooter,
 | 
			
		||||
    Accordion,
 | 
			
		||||
    AccordionItem,
 | 
			
		||||
  } from "sveltestrap";
 | 
			
		||||
  import { onMount, onDestroy } from "svelte";
 | 
			
		||||
 | 
			
		||||
  let screenSize = window.innerWidth;
 | 
			
		||||
 | 
			
		||||
  function updateScreenSize() {
 | 
			
		||||
    screenSize = window.innerWidth;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMount(() => {
 | 
			
		||||
    window.addEventListener("resize", updateScreenSize);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onDestroy(() => {
 | 
			
		||||
    window.removeEventListener("resize", updateScreenSize);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  import {
 | 
			
		||||
    init,
 | 
			
		||||
    convert2uplot,
 | 
			
		||||
    transformPerNodeDataForRoofline,
 | 
			
		||||
  } from "./utils.js";
 | 
			
		||||
  import { scaleNumbers } from "./units.js";
 | 
			
		||||
  import {
 | 
			
		||||
    queryStore,
 | 
			
		||||
    gql,
 | 
			
		||||
    getContextClient,
 | 
			
		||||
    mutationStore,
 | 
			
		||||
  } from "@urql/svelte";
 | 
			
		||||
  import PlotTable from "./PlotTable.svelte";
 | 
			
		||||
  import HistogramSelection from "./HistogramSelection.svelte";
 | 
			
		||||
  import ClusterMachine from "./partition/ClusterMachine.svelte";
 | 
			
		||||
 | 
			
		||||
  const { query: initq } = init();
 | 
			
		||||
  const ccconfig = getContext("cc-config");
 | 
			
		||||
 | 
			
		||||
  export let cluster;
 | 
			
		||||
 | 
			
		||||
  let plotWidths = [],
 | 
			
		||||
    colWidth1,
 | 
			
		||||
    colWidth2;
 | 
			
		||||
  let from = new Date(Date.now() - 5 * 60 * 1000),
 | 
			
		||||
    to = new Date(Date.now());
 | 
			
		||||
  const topOptions = [
 | 
			
		||||
    { key: "totalJobs", label: "Jobs" },
 | 
			
		||||
    { key: "totalNodes", label: "Nodes" },
 | 
			
		||||
    { key: "totalCores", label: "Cores" },
 | 
			
		||||
    { key: "totalAccs", label: "Accelerators" },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  let topProjectSelection =
 | 
			
		||||
    topOptions.find(
 | 
			
		||||
      (option) =>
 | 
			
		||||
        option.key ==
 | 
			
		||||
        ccconfig[`status_view_selectedTopProjectCategory:${cluster}`]
 | 
			
		||||
    ) ||
 | 
			
		||||
    topOptions.find(
 | 
			
		||||
      (option) => option.key == ccconfig.status_view_selectedTopProjectCategory
 | 
			
		||||
    );
 | 
			
		||||
  let topUserSelection =
 | 
			
		||||
    topOptions.find(
 | 
			
		||||
      (option) =>
 | 
			
		||||
        option.key == ccconfig[`status_view_selectedTopUserCategory:${cluster}`]
 | 
			
		||||
    ) ||
 | 
			
		||||
    topOptions.find(
 | 
			
		||||
      (option) => option.key == ccconfig.status_view_selectedTopUserCategory
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let isHistogramSelectionOpen = false;
 | 
			
		||||
  $: metricsInHistograms = cluster
 | 
			
		||||
    ? ccconfig[`user_view_histogramMetrics:${cluster}`] || []
 | 
			
		||||
    : ccconfig.user_view_histogramMetrics || [];
 | 
			
		||||
 | 
			
		||||
  const client = getContextClient();
 | 
			
		||||
  $: mainQuery = queryStore({
 | 
			
		||||
    client: client,
 | 
			
		||||
    query: gql`
 | 
			
		||||
      query (
 | 
			
		||||
        $cluster: String!
 | 
			
		||||
        $filter: [JobFilter!]!
 | 
			
		||||
        $metrics: [String!]
 | 
			
		||||
        $from: Time!
 | 
			
		||||
        $to: Time!
 | 
			
		||||
        $metricsInHistograms: [String!]
 | 
			
		||||
      ) {
 | 
			
		||||
        nodeMetrics(
 | 
			
		||||
          cluster: $cluster
 | 
			
		||||
          metrics: $metrics
 | 
			
		||||
          from: $from
 | 
			
		||||
          to: $to
 | 
			
		||||
        ) {
 | 
			
		||||
          host
 | 
			
		||||
          subCluster
 | 
			
		||||
          metrics {
 | 
			
		||||
            name
 | 
			
		||||
            scope
 | 
			
		||||
            metric {
 | 
			
		||||
              timestep
 | 
			
		||||
              unit {
 | 
			
		||||
                base
 | 
			
		||||
                prefix
 | 
			
		||||
              }
 | 
			
		||||
              series {
 | 
			
		||||
                data
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
 | 
			
		||||
          histDuration {
 | 
			
		||||
            count
 | 
			
		||||
            value
 | 
			
		||||
          }
 | 
			
		||||
          histNumNodes {
 | 
			
		||||
            count
 | 
			
		||||
            value
 | 
			
		||||
          }
 | 
			
		||||
          histNumCores {
 | 
			
		||||
            count
 | 
			
		||||
            value
 | 
			
		||||
          }
 | 
			
		||||
          histNumAccs {
 | 
			
		||||
            count
 | 
			
		||||
            value
 | 
			
		||||
          }
 | 
			
		||||
          histMetrics {
 | 
			
		||||
            metric
 | 
			
		||||
            unit
 | 
			
		||||
            data {
 | 
			
		||||
              min
 | 
			
		||||
              max
 | 
			
		||||
              count
 | 
			
		||||
              bin
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        allocatedNodes(cluster: $cluster) {
 | 
			
		||||
          name
 | 
			
		||||
          count
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    `,
 | 
			
		||||
    variables: {
 | 
			
		||||
      cluster: cluster,
 | 
			
		||||
      metrics: ["flops_any", "mem_bw"],
 | 
			
		||||
      from: from.toISOString(),
 | 
			
		||||
      to: to.toISOString(),
 | 
			
		||||
      filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
      metricsInHistograms: metricsInHistograms,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const paging = { itemsPerPage: 10, page: 1 }; // Top 10
 | 
			
		||||
  $: topUserQuery = queryStore({
 | 
			
		||||
    client: client,
 | 
			
		||||
    query: gql`
 | 
			
		||||
      query (
 | 
			
		||||
        $filter: [JobFilter!]!
 | 
			
		||||
        $paging: PageRequest!
 | 
			
		||||
        $sortBy: SortByAggregate!
 | 
			
		||||
      ) {
 | 
			
		||||
        topUser: jobsStatistics(
 | 
			
		||||
          filter: $filter
 | 
			
		||||
          page: $paging
 | 
			
		||||
          sortBy: $sortBy
 | 
			
		||||
          groupBy: USER
 | 
			
		||||
        ) {
 | 
			
		||||
          id
 | 
			
		||||
          totalJobs
 | 
			
		||||
          totalNodes
 | 
			
		||||
          totalCores
 | 
			
		||||
          totalAccs
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    `,
 | 
			
		||||
    variables: {
 | 
			
		||||
      filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
      paging,
 | 
			
		||||
      sortBy: topUserSelection.key.toUpperCase(),
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  $: topProjectQuery = queryStore({
 | 
			
		||||
    client: client,
 | 
			
		||||
    query: gql`
 | 
			
		||||
      query (
 | 
			
		||||
        $filter: [JobFilter!]!
 | 
			
		||||
        $paging: PageRequest!
 | 
			
		||||
        $sortBy: SortByAggregate!
 | 
			
		||||
      ) {
 | 
			
		||||
        topProjects: jobsStatistics(
 | 
			
		||||
          filter: $filter
 | 
			
		||||
          page: $paging
 | 
			
		||||
          sortBy: $sortBy
 | 
			
		||||
          groupBy: PROJECT
 | 
			
		||||
        ) {
 | 
			
		||||
          id
 | 
			
		||||
          totalJobs
 | 
			
		||||
          totalNodes
 | 
			
		||||
          totalCores
 | 
			
		||||
          totalAccs
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    `,
 | 
			
		||||
    variables: {
 | 
			
		||||
      filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
      paging,
 | 
			
		||||
      sortBy: topProjectSelection.key.toUpperCase(),
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const sumUp = (data, subcluster, metric) =>
 | 
			
		||||
    data.reduce(
 | 
			
		||||
      (sum, node) =>
 | 
			
		||||
        node.subCluster == subcluster
 | 
			
		||||
          ? sum +
 | 
			
		||||
            (node.metrics
 | 
			
		||||
              .find((m) => m.name == metric)
 | 
			
		||||
              ?.metric.series.reduce(
 | 
			
		||||
                (sum, series) => sum + series.data[series.data.length - 1],
 | 
			
		||||
                0
 | 
			
		||||
              ) || 0)
 | 
			
		||||
          : sum,
 | 
			
		||||
      0
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let allocatedNodes = {},
 | 
			
		||||
    flopRate = {},
 | 
			
		||||
    flopRateUnitPrefix = {},
 | 
			
		||||
    flopRateUnitBase = {},
 | 
			
		||||
    memBwRate = {},
 | 
			
		||||
    memBwRateUnitPrefix = {},
 | 
			
		||||
    memBwRateUnitBase = {};
 | 
			
		||||
  $: if ($initq.data && $mainQuery.data) {
 | 
			
		||||
    let subClusters = $initq.data.clusters.find(
 | 
			
		||||
      (c) => c.name == cluster
 | 
			
		||||
    ).subClusters;
 | 
			
		||||
    for (let subCluster of subClusters) {
 | 
			
		||||
      allocatedNodes[subCluster.name] =
 | 
			
		||||
        $mainQuery.data.allocatedNodes.find(
 | 
			
		||||
          ({ name }) => name == subCluster.name
 | 
			
		||||
        )?.count || 0;
 | 
			
		||||
      flopRate[subCluster.name] =
 | 
			
		||||
        Math.floor(
 | 
			
		||||
          sumUp($mainQuery.data.nodeMetrics, subCluster.name, "flops_any") * 100
 | 
			
		||||
        ) / 100;
 | 
			
		||||
      flopRateUnitPrefix[subCluster.name] = subCluster.flopRateSimd.unit.prefix;
 | 
			
		||||
      flopRateUnitBase[subCluster.name] = subCluster.flopRateSimd.unit.base;
 | 
			
		||||
      memBwRate[subCluster.name] =
 | 
			
		||||
        Math.floor(
 | 
			
		||||
          sumUp($mainQuery.data.nodeMetrics, subCluster.name, "mem_bw") * 100
 | 
			
		||||
        ) / 100;
 | 
			
		||||
      memBwRateUnitPrefix[subCluster.name] =
 | 
			
		||||
        subCluster.memoryBandwidth.unit.prefix;
 | 
			
		||||
      memBwRateUnitBase[subCluster.name] = subCluster.memoryBandwidth.unit.base;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const updateConfigurationMutation = ({ name, value }) => {
 | 
			
		||||
    return mutationStore({
 | 
			
		||||
      client: client,
 | 
			
		||||
      query: gql`
 | 
			
		||||
        mutation ($name: String!, $value: String!) {
 | 
			
		||||
          updateConfiguration(name: $name, value: $value)
 | 
			
		||||
        }
 | 
			
		||||
      `,
 | 
			
		||||
      variables: { name, value },
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  function updateTopUserConfiguration(select) {
 | 
			
		||||
    if (ccconfig[`status_view_selectedTopUserCategory:${cluster}`] != select) {
 | 
			
		||||
      updateConfigurationMutation({
 | 
			
		||||
        name: `status_view_selectedTopUserCategory:${cluster}`,
 | 
			
		||||
        value: JSON.stringify(select),
 | 
			
		||||
      }).subscribe((res) => {
 | 
			
		||||
        if (res.fetching === false && !res.error) {
 | 
			
		||||
          // console.log(`status_view_selectedTopUserCategory:${cluster}` + ' -> Updated!')
 | 
			
		||||
        } else if (res.fetching === false && res.error) {
 | 
			
		||||
          throw res.error;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      // console.log('No Mutation Required: Top User')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function updateTopProjectConfiguration(select) {
 | 
			
		||||
    if (
 | 
			
		||||
      ccconfig[`status_view_selectedTopProjectCategory:${cluster}`] != select
 | 
			
		||||
    ) {
 | 
			
		||||
      updateConfigurationMutation({
 | 
			
		||||
        name: `status_view_selectedTopProjectCategory:${cluster}`,
 | 
			
		||||
        value: JSON.stringify(select),
 | 
			
		||||
      }).subscribe((res) => {
 | 
			
		||||
        if (res.fetching === false && !res.error) {
 | 
			
		||||
          // console.log(`status_view_selectedTopProjectCategory:${cluster}` + ' -> Updated!')
 | 
			
		||||
        } else if (res.fetching === false && res.error) {
 | 
			
		||||
          throw res.error;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      // console.log('No Mutation Required: Top Project')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $: updateTopUserConfiguration(topUserSelection.key);
 | 
			
		||||
  $: updateTopProjectConfiguration(topProjectSelection.key);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- Loading indicator & Refresh -->
 | 
			
		||||
 | 
			
		||||
<Row>
 | 
			
		||||
  <Col xs="auto" style="align-self: flex-end;">
 | 
			
		||||
    <h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
 | 
			
		||||
  </Col>
 | 
			
		||||
  <Col xs="auto" style="margin-left: 0.25rem;">
 | 
			
		||||
    {#if $initq.fetching || $mainQuery.fetching}
 | 
			
		||||
      <Spinner />
 | 
			
		||||
    {:else if $initq.error}
 | 
			
		||||
      <Card body color="danger">{$initq.error.message}</Card>
 | 
			
		||||
    {:else}
 | 
			
		||||
      <!-- ... -->
 | 
			
		||||
    {/if}
 | 
			
		||||
  </Col>
 | 
			
		||||
  <Col xs="auto" style="margin-left: auto;">
 | 
			
		||||
    <Button
 | 
			
		||||
      outline
 | 
			
		||||
      color="secondary"
 | 
			
		||||
      on:click={() => (isHistogramSelectionOpen = true)}
 | 
			
		||||
    >
 | 
			
		||||
      <Icon name="bar-chart-line" /> Select Histograms
 | 
			
		||||
    </Button>
 | 
			
		||||
  </Col>
 | 
			
		||||
  <Col xs="auto" style="margin-left: 0.25rem;">
 | 
			
		||||
    <Refresher
 | 
			
		||||
      initially={120}
 | 
			
		||||
      on:reload={() => {
 | 
			
		||||
        from = new Date(Date.now() - 5 * 60 * 1000);
 | 
			
		||||
        to = new Date(Date.now());
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  </Col>
 | 
			
		||||
</Row>
 | 
			
		||||
{#if $mainQuery.error}
 | 
			
		||||
  <Row>
 | 
			
		||||
    <Col>
 | 
			
		||||
      <Card body color="danger">{$mainQuery.error.message}</Card>
 | 
			
		||||
    </Col>
 | 
			
		||||
  </Row>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
<hr />
 | 
			
		||||
 | 
			
		||||
<ClusterMachine />  
 | 
			
		||||
@@ -89,10 +89,11 @@
 | 
			
		||||
      menu: "Groups",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Status",
 | 
			
		||||
      title: "Control",
 | 
			
		||||
      requiredRole: roles.admin,
 | 
			
		||||
      href: "/monitoring/status/",
 | 
			
		||||
      icon: "cpu",
 | 
			
		||||
      href: "/monitoring/control/",
 | 
			
		||||
    //   icontype : "lucide",
 | 
			
		||||
      icon: "folder-symlink-fill",
 | 
			
		||||
      perCluster: true,
 | 
			
		||||
      menu: "Stats",
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,48 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import {
 | 
			
		||||
        Icon,
 | 
			
		||||
        NavLink,
 | 
			
		||||
        Dropdown,
 | 
			
		||||
        DropdownToggle,
 | 
			
		||||
        DropdownMenu,
 | 
			
		||||
        DropdownItem,
 | 
			
		||||
    } from "sveltestrap";
 | 
			
		||||
  import {
 | 
			
		||||
    Icon,
 | 
			
		||||
    NavLink,
 | 
			
		||||
    Dropdown,
 | 
			
		||||
    DropdownToggle,
 | 
			
		||||
    DropdownMenu,
 | 
			
		||||
    DropdownItem,
 | 
			
		||||
  } from "sveltestrap";
 | 
			
		||||
  // import  {lucideIcon as} from "lucide-svelte";
 | 
			
		||||
 | 
			
		||||
    export let clusters; // array of names
 | 
			
		||||
    export let links; // array of nav links
 | 
			
		||||
  export let clusters; // array of names
 | 
			
		||||
  export let links; // array of nav links
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#each links as item}
 | 
			
		||||
    {#if !item.perCluster}
 | 
			
		||||
        <NavLink href={item.href} active={window.location.pathname == item.href}
 | 
			
		||||
            ><Icon name={item.icon} /> {item.title}</NavLink
 | 
			
		||||
        >
 | 
			
		||||
    {:else}
 | 
			
		||||
        <Dropdown nav inNavbar>
 | 
			
		||||
            <DropdownToggle nav caret>
 | 
			
		||||
                <Icon name={item.icon} />
 | 
			
		||||
                {item.title}
 | 
			
		||||
            </DropdownToggle>
 | 
			
		||||
            <DropdownMenu class="dropdown-menu-lg-end">
 | 
			
		||||
                {#each clusters as cluster}
 | 
			
		||||
                    <DropdownItem
 | 
			
		||||
                        href={item.href + cluster.name}
 | 
			
		||||
                        active={window.location.pathname ==
 | 
			
		||||
                            item.href + cluster.name}
 | 
			
		||||
                    >
 | 
			
		||||
                        {cluster.name}
 | 
			
		||||
                    </DropdownItem>
 | 
			
		||||
                {/each}
 | 
			
		||||
            </DropdownMenu>
 | 
			
		||||
        </Dropdown>
 | 
			
		||||
    {/if}
 | 
			
		||||
  {#if !item.perCluster}
 | 
			
		||||
    <NavLink href={item.href} active={window.location.pathname == item.href}
 | 
			
		||||
      ><Icon name={item.icon} /> {item.title}</NavLink
 | 
			
		||||
    >
 | 
			
		||||
  {:else}
 | 
			
		||||
    <Dropdown nav inNavbar>
 | 
			
		||||
      <DropdownToggle nav caret>
 | 
			
		||||
        {#if item.icontype === "lucide"}
 | 
			
		||||
          <script>
 | 
			
		||||
                import {item.icon} from "lucide-svelte";
 | 
			
		||||
                console.log(item.icon);
 | 
			
		||||
          </script>
 | 
			
		||||
          
 | 
			
		||||
          <item.icon  />
 | 
			
		||||
        {:else}
 | 
			
		||||
          <Icon name={item.icon} />
 | 
			
		||||
        {/if}
 | 
			
		||||
        {item.title}
 | 
			
		||||
      </DropdownToggle>
 | 
			
		||||
      <DropdownMenu class="dropdown-menu-lg-end">
 | 
			
		||||
        {#each clusters as cluster}
 | 
			
		||||
          <DropdownItem
 | 
			
		||||
            href={item.href + cluster.name}
 | 
			
		||||
            active={window.location.pathname == item.href + cluster.name}
 | 
			
		||||
          >
 | 
			
		||||
            {cluster.name}
 | 
			
		||||
          </DropdownItem>
 | 
			
		||||
        {/each}
 | 
			
		||||
      </DropdownMenu>
 | 
			
		||||
    </Dropdown>
 | 
			
		||||
  {/if}
 | 
			
		||||
{/each}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
    import VerticalTab from "./partition/VerticalTab.svelte";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    export let cluster;
 | 
			
		||||
    // export let cluster;
 | 
			
		||||
    export let from = null;
 | 
			
		||||
    export let to = null;
 | 
			
		||||
 | 
			
		||||
@@ -42,96 +42,96 @@
 | 
			
		||||
    const clusters = getContext("clusters");
 | 
			
		||||
    console.log(clusters);
 | 
			
		||||
    const ccconfig = getContext("cc-config");
 | 
			
		||||
    const metricConfig = getContext("metrics");
 | 
			
		||||
    // const metricConfig = getContext("metrics");
 | 
			
		||||
 | 
			
		||||
    let plotHeight = 300;
 | 
			
		||||
    let hostnameFilter = "";
 | 
			
		||||
    let selectedMetric = ccconfig.system_view_selectedMetric;
 | 
			
		||||
 | 
			
		||||
    const client = getContextClient();
 | 
			
		||||
    $: nodesQuery = queryStore({
 | 
			
		||||
        client: client,
 | 
			
		||||
        query: gql`
 | 
			
		||||
            query (
 | 
			
		||||
                $cluster: String!
 | 
			
		||||
                $metrics: [String!]
 | 
			
		||||
                $from: Time!
 | 
			
		||||
                $to: Time!
 | 
			
		||||
            ) {
 | 
			
		||||
                nodeMetrics(
 | 
			
		||||
                    cluster: $cluster
 | 
			
		||||
                    metrics: $metrics
 | 
			
		||||
                    from: $from
 | 
			
		||||
                    to: $to
 | 
			
		||||
                ) {
 | 
			
		||||
                    host
 | 
			
		||||
                    subCluster
 | 
			
		||||
                    metrics {
 | 
			
		||||
                        name
 | 
			
		||||
                        scope
 | 
			
		||||
                        metric {
 | 
			
		||||
                            timestep
 | 
			
		||||
                            unit {
 | 
			
		||||
                                base
 | 
			
		||||
                                prefix
 | 
			
		||||
                            }
 | 
			
		||||
                            series {
 | 
			
		||||
                                statistics {
 | 
			
		||||
                                    min
 | 
			
		||||
                                    avg
 | 
			
		||||
                                    max
 | 
			
		||||
                                }
 | 
			
		||||
                                data
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        `,
 | 
			
		||||
        variables: {
 | 
			
		||||
            cluster: cluster,
 | 
			
		||||
            metrics: [selectedMetric],
 | 
			
		||||
            from: from.toISOString(),
 | 
			
		||||
            to: to.toISOString(),
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    // const client = getContextClient();
 | 
			
		||||
    // $: nodesQuery = queryStore({
 | 
			
		||||
    //     client: client,
 | 
			
		||||
    //     query: gql`
 | 
			
		||||
    //         query (
 | 
			
		||||
    //             $cluster: String!
 | 
			
		||||
    //             $metrics: [String!]
 | 
			
		||||
    //             $from: Time!
 | 
			
		||||
    //             $to: Time!
 | 
			
		||||
    //         ) {
 | 
			
		||||
    //             nodeMetrics(
 | 
			
		||||
    //                 cluster: $cluster
 | 
			
		||||
    //                 metrics: $metrics
 | 
			
		||||
    //                 from: $from
 | 
			
		||||
    //                 to: $to
 | 
			
		||||
    //             ) {
 | 
			
		||||
    //                 host
 | 
			
		||||
    //                 subCluster
 | 
			
		||||
    //                 metrics {
 | 
			
		||||
    //                     name
 | 
			
		||||
    //                     scope
 | 
			
		||||
    //                     metric {
 | 
			
		||||
    //                         timestep
 | 
			
		||||
    //                         unit {
 | 
			
		||||
    //                             base
 | 
			
		||||
    //                             prefix
 | 
			
		||||
    //                         }
 | 
			
		||||
    //                         series {
 | 
			
		||||
    //                             statistics {
 | 
			
		||||
    //                                 min
 | 
			
		||||
    //                                 avg
 | 
			
		||||
    //                                 max
 | 
			
		||||
    //                             }
 | 
			
		||||
    //                             data
 | 
			
		||||
    //                         }
 | 
			
		||||
    //                     }
 | 
			
		||||
    //                 }
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     `,
 | 
			
		||||
    //     variables: {
 | 
			
		||||
    //         cluster: cluster,
 | 
			
		||||
    //         metrics: [selectedMetric],
 | 
			
		||||
    //         from: from.toISOString(),
 | 
			
		||||
    //         to: to.toISOString(),
 | 
			
		||||
    //     },
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    let metricUnits = {};
 | 
			
		||||
    $: if ($nodesQuery.data) {
 | 
			
		||||
        let thisCluster = clusters.find((c) => c.name == cluster);
 | 
			
		||||
        if (thisCluster) {
 | 
			
		||||
            for (let metric of thisCluster.metricConfig) {
 | 
			
		||||
                if (metric.unit.prefix || metric.unit.base) {
 | 
			
		||||
                    metricUnits[metric.name] =
 | 
			
		||||
                        "(" +
 | 
			
		||||
                        (metric.unit.prefix ? metric.unit.prefix : "") +
 | 
			
		||||
                        (metric.unit.base ? metric.unit.base : "") +
 | 
			
		||||
                        ")";
 | 
			
		||||
                } else {
 | 
			
		||||
                    // If no unit defined: Omit Unit Display
 | 
			
		||||
                    metricUnits[metric.name] = "";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // let metricUnits = {};
 | 
			
		||||
    // $: if ($nodesQuery.data) {
 | 
			
		||||
    //     let thisCluster = clusters.find((c) => c.name == cluster);
 | 
			
		||||
    //     if (thisCluster) {
 | 
			
		||||
    //         for (let metric of thisCluster.metricConfig) {
 | 
			
		||||
    //             if (metric.unit.prefix || metric.unit.base) {
 | 
			
		||||
    //                 metricUnits[metric.name] =
 | 
			
		||||
    //                     "(" +
 | 
			
		||||
    //                     (metric.unit.prefix ? metric.unit.prefix : "") +
 | 
			
		||||
    //                     (metric.unit.base ? metric.unit.base : "") +
 | 
			
		||||
    //                     ")";
 | 
			
		||||
    //             } else {
 | 
			
		||||
    //                 // If no unit defined: Omit Unit Display
 | 
			
		||||
    //                 metricUnits[metric.name] = "";
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    let notifications = [
 | 
			
		||||
        {
 | 
			
		||||
            type: "success",
 | 
			
		||||
            message: "This is a success notification!",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            type: "error",
 | 
			
		||||
            message: "An error occurred.",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            type: "info",
 | 
			
		||||
            message: "Just a friendly reminder.",
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    // let notifications = [
 | 
			
		||||
    //     {
 | 
			
		||||
    //         type: "success",
 | 
			
		||||
    //         message: "This is a success notification!",
 | 
			
		||||
    //     },
 | 
			
		||||
    //     {
 | 
			
		||||
    //         type: "error",
 | 
			
		||||
    //         message: "An error occurred.",
 | 
			
		||||
    //     },
 | 
			
		||||
    //     {
 | 
			
		||||
    //         type: "info",
 | 
			
		||||
    //         message: "Just a friendly reminder.",
 | 
			
		||||
    //     },
 | 
			
		||||
    // ];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Row>
 | 
			
		||||
<!-- <Row>
 | 
			
		||||
    {#if $initq.error}
 | 
			
		||||
        <Card body color="danger">{$initq.error.message}</Card>
 | 
			
		||||
    {:else if $initq.fetching}
 | 
			
		||||
@@ -175,7 +175,7 @@
 | 
			
		||||
        </Col>
 | 
			
		||||
    {/if}
 | 
			
		||||
</Row>
 | 
			
		||||
<br />
 | 
			
		||||
<br /> -->
 | 
			
		||||
 | 
			
		||||
<Card>
 | 
			
		||||
    <CardHeader>
 | 
			
		||||
@@ -187,36 +187,14 @@
 | 
			
		||||
    </CardBody>
 | 
			
		||||
</Card>
 | 
			
		||||
<br />
 | 
			
		||||
<!-- notification -->
 | 
			
		||||
<!-- <Accordion open={0}>
 | 
			
		||||
    {#each notifications as notification, i}
 | 
			
		||||
        <AccordionItem key={i}>
 | 
			
		||||
            <div
 | 
			
		||||
                class="d-flex justify-content-between align-items-center"
 | 
			
		||||
                role="button"
 | 
			
		||||
                tabindex={i}
 | 
			
		||||
            >
 | 
			
		||||
                <span class="me-2">
 | 
			
		||||
                    <i class={`bi bi-circle-fill text-${notification.type}`}
 | 
			
		||||
                    ></i>
 | 
			
		||||
                    {notification.type.toUpperCase()}
 | 
			
		||||
                </span>
 | 
			
		||||
                <i class="bi bi-chevron-down" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="collapse show">
 | 
			
		||||
                {notification.message}
 | 
			
		||||
            </div>
 | 
			
		||||
        </AccordionItem>
 | 
			
		||||
    {/each}
 | 
			
		||||
</Accordion> -->
 | 
			
		||||
<!-- <br /> -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<Card class="mb-1">
 | 
			
		||||
    <CardHeader>
 | 
			
		||||
        <CardTitle>Partition Configuration</CardTitle>
 | 
			
		||||
        <CardTitle>Host Cluster Configuration</CardTitle>
 | 
			
		||||
    </CardHeader>
 | 
			
		||||
    <CardBody class="h5">
 | 
			
		||||
        <CardSubtitle>Create and manage LVM partitions</CardSubtitle>
 | 
			
		||||
        <CardSubtitle></CardSubtitle>
 | 
			
		||||
        <CardText></CardText>
 | 
			
		||||
        <VerticalTab />
 | 
			
		||||
    </CardBody>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,725 +0,0 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import { getContext } from "svelte";
 | 
			
		||||
    import Refresher from "./joblist/Refresher.svelte";
 | 
			
		||||
    import Roofline from "./plots/Roofline.svelte";
 | 
			
		||||
    import Pie, { colors } from "./plots/Pie.svelte";
 | 
			
		||||
    import Histogram from "./plots/Histogram.svelte";
 | 
			
		||||
    import {
 | 
			
		||||
        Row,
 | 
			
		||||
        Col,
 | 
			
		||||
        Spinner,
 | 
			
		||||
        Card,
 | 
			
		||||
        CardHeader,
 | 
			
		||||
        CardTitle,
 | 
			
		||||
        CardBody,
 | 
			
		||||
        Table,
 | 
			
		||||
        Progress,
 | 
			
		||||
        Icon,
 | 
			
		||||
        Button
 | 
			
		||||
    } from "sveltestrap";
 | 
			
		||||
    import { init, convert2uplot, transformPerNodeDataForRoofline } from "./utils.js";
 | 
			
		||||
    import { scaleNumbers } from "./units.js";
 | 
			
		||||
    import {
 | 
			
		||||
        queryStore,
 | 
			
		||||
        gql,
 | 
			
		||||
        getContextClient,
 | 
			
		||||
        mutationStore,
 | 
			
		||||
    } from "@urql/svelte";
 | 
			
		||||
    import PlotTable from './PlotTable.svelte'
 | 
			
		||||
    import HistogramSelection from './HistogramSelection.svelte'
 | 
			
		||||
 | 
			
		||||
    const { query: initq } = init();
 | 
			
		||||
    const ccconfig = getContext("cc-config");
 | 
			
		||||
 | 
			
		||||
    export let cluster;
 | 
			
		||||
 | 
			
		||||
    let plotWidths = [],
 | 
			
		||||
        colWidth1,
 | 
			
		||||
        colWidth2
 | 
			
		||||
    let from = new Date(Date.now() - 5 * 60 * 1000),
 | 
			
		||||
        to = new Date(Date.now());
 | 
			
		||||
    const topOptions = [
 | 
			
		||||
        { key: "totalJobs", label: "Jobs" },
 | 
			
		||||
        { key: "totalNodes", label: "Nodes" },
 | 
			
		||||
        { key: "totalCores", label: "Cores" },
 | 
			
		||||
        { key: "totalAccs", label: "Accelerators" },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    let topProjectSelection =
 | 
			
		||||
        topOptions.find(
 | 
			
		||||
            (option) =>
 | 
			
		||||
                option.key ==
 | 
			
		||||
                ccconfig[`status_view_selectedTopProjectCategory:${cluster}`]
 | 
			
		||||
        ) ||
 | 
			
		||||
        topOptions.find(
 | 
			
		||||
            (option) =>
 | 
			
		||||
                option.key == ccconfig.status_view_selectedTopProjectCategory
 | 
			
		||||
        );
 | 
			
		||||
    let topUserSelection =
 | 
			
		||||
        topOptions.find(
 | 
			
		||||
            (option) =>
 | 
			
		||||
                option.key ==
 | 
			
		||||
                ccconfig[`status_view_selectedTopUserCategory:${cluster}`]
 | 
			
		||||
        ) ||
 | 
			
		||||
        topOptions.find(
 | 
			
		||||
            (option) =>
 | 
			
		||||
                option.key == ccconfig.status_view_selectedTopUserCategory
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    let isHistogramSelectionOpen = false
 | 
			
		||||
    $: metricsInHistograms = cluster ? (ccconfig[`user_view_histogramMetrics:${cluster}`] || []) : (ccconfig.user_view_histogramMetrics || [])
 | 
			
		||||
 | 
			
		||||
    const client = getContextClient();
 | 
			
		||||
    $: mainQuery = queryStore({
 | 
			
		||||
        client: client,
 | 
			
		||||
        query: gql`
 | 
			
		||||
            query (
 | 
			
		||||
                $cluster: String!
 | 
			
		||||
                $filter: [JobFilter!]!
 | 
			
		||||
                $metrics: [String!]
 | 
			
		||||
                $from: Time!
 | 
			
		||||
                $to: Time!
 | 
			
		||||
                $metricsInHistograms: [String!]
 | 
			
		||||
            ) {
 | 
			
		||||
                nodeMetrics(
 | 
			
		||||
                    cluster: $cluster
 | 
			
		||||
                    metrics: $metrics
 | 
			
		||||
                    from: $from
 | 
			
		||||
                    to: $to
 | 
			
		||||
                ) {
 | 
			
		||||
                    host
 | 
			
		||||
                    subCluster
 | 
			
		||||
                    metrics {
 | 
			
		||||
                        name
 | 
			
		||||
                        scope
 | 
			
		||||
                        metric {
 | 
			
		||||
                            timestep
 | 
			
		||||
                            unit {
 | 
			
		||||
                                base
 | 
			
		||||
                                prefix
 | 
			
		||||
                            }
 | 
			
		||||
                            series {
 | 
			
		||||
                                data
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
 | 
			
		||||
                    histDuration {
 | 
			
		||||
                        count
 | 
			
		||||
                        value
 | 
			
		||||
                    }
 | 
			
		||||
                    histNumNodes {
 | 
			
		||||
                        count
 | 
			
		||||
                        value
 | 
			
		||||
                    }
 | 
			
		||||
                    histNumCores {
 | 
			
		||||
                        count
 | 
			
		||||
                        value
 | 
			
		||||
                    }
 | 
			
		||||
                    histNumAccs {
 | 
			
		||||
                        count
 | 
			
		||||
                        value
 | 
			
		||||
                    }
 | 
			
		||||
                    histMetrics  { 
 | 
			
		||||
                        metric
 | 
			
		||||
                        unit
 | 
			
		||||
                        data { 
 | 
			
		||||
                            min
 | 
			
		||||
                            max
 | 
			
		||||
                            count
 | 
			
		||||
                            bin
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                allocatedNodes(cluster: $cluster) {
 | 
			
		||||
                    name
 | 
			
		||||
                    count
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        `,
 | 
			
		||||
        variables: {
 | 
			
		||||
            cluster: cluster,
 | 
			
		||||
            metrics: ["flops_any", "mem_bw"],
 | 
			
		||||
            from: from.toISOString(),
 | 
			
		||||
            to: to.toISOString(),
 | 
			
		||||
            filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
            metricsInHistograms: metricsInHistograms
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const paging = { itemsPerPage: 10, page: 1 }; // Top 10
 | 
			
		||||
    $: topUserQuery = queryStore({
 | 
			
		||||
        client: client,
 | 
			
		||||
        query: gql`
 | 
			
		||||
            query (
 | 
			
		||||
                $filter: [JobFilter!]!
 | 
			
		||||
                $paging: PageRequest!
 | 
			
		||||
                $sortBy: SortByAggregate!
 | 
			
		||||
            ) {
 | 
			
		||||
                topUser: jobsStatistics(
 | 
			
		||||
                    filter: $filter
 | 
			
		||||
                    page: $paging
 | 
			
		||||
                    sortBy: $sortBy
 | 
			
		||||
                    groupBy: USER
 | 
			
		||||
                ) {
 | 
			
		||||
                    id
 | 
			
		||||
                    totalJobs
 | 
			
		||||
                    totalNodes
 | 
			
		||||
                    totalCores
 | 
			
		||||
                    totalAccs
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        `,
 | 
			
		||||
        variables: {
 | 
			
		||||
            filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
            paging,
 | 
			
		||||
            sortBy: topUserSelection.key.toUpperCase(),
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $: topProjectQuery = queryStore({
 | 
			
		||||
        client: client,
 | 
			
		||||
        query: gql`
 | 
			
		||||
            query (
 | 
			
		||||
                $filter: [JobFilter!]!
 | 
			
		||||
                $paging: PageRequest!
 | 
			
		||||
                $sortBy: SortByAggregate!
 | 
			
		||||
            ) {
 | 
			
		||||
                topProjects: jobsStatistics(
 | 
			
		||||
                    filter: $filter
 | 
			
		||||
                    page: $paging
 | 
			
		||||
                    sortBy: $sortBy
 | 
			
		||||
                    groupBy: PROJECT
 | 
			
		||||
                ) {
 | 
			
		||||
                    id
 | 
			
		||||
                    totalJobs
 | 
			
		||||
                    totalNodes
 | 
			
		||||
                    totalCores
 | 
			
		||||
                    totalAccs
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        `,
 | 
			
		||||
        variables: {
 | 
			
		||||
            filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
 | 
			
		||||
            paging,
 | 
			
		||||
            sortBy: topProjectSelection.key.toUpperCase(),
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const sumUp = (data, subcluster, metric) =>
 | 
			
		||||
        data.reduce(
 | 
			
		||||
            (sum, node) =>
 | 
			
		||||
                node.subCluster == subcluster
 | 
			
		||||
                    ? sum +
 | 
			
		||||
                      (node.metrics
 | 
			
		||||
                          .find((m) => m.name == metric)
 | 
			
		||||
                          ?.metric.series.reduce(
 | 
			
		||||
                              (sum, series) =>
 | 
			
		||||
                                  sum + series.data[series.data.length - 1],
 | 
			
		||||
                              0
 | 
			
		||||
                          ) || 0)
 | 
			
		||||
                    : sum,
 | 
			
		||||
            0
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    let allocatedNodes = {},
 | 
			
		||||
        flopRate = {},
 | 
			
		||||
        flopRateUnitPrefix = {},
 | 
			
		||||
        flopRateUnitBase = {},
 | 
			
		||||
        memBwRate = {},
 | 
			
		||||
        memBwRateUnitPrefix = {},
 | 
			
		||||
        memBwRateUnitBase = {};
 | 
			
		||||
    $: if ($initq.data && $mainQuery.data) {
 | 
			
		||||
        let subClusters = $initq.data.clusters.find(
 | 
			
		||||
            (c) => c.name == cluster
 | 
			
		||||
        ).subClusters;
 | 
			
		||||
        for (let subCluster of subClusters) {
 | 
			
		||||
            allocatedNodes[subCluster.name] =
 | 
			
		||||
                $mainQuery.data.allocatedNodes.find(
 | 
			
		||||
                    ({ name }) => name == subCluster.name
 | 
			
		||||
                )?.count || 0;
 | 
			
		||||
            flopRate[subCluster.name] =
 | 
			
		||||
                Math.floor(
 | 
			
		||||
                    sumUp(
 | 
			
		||||
                        $mainQuery.data.nodeMetrics,
 | 
			
		||||
                        subCluster.name,
 | 
			
		||||
                        "flops_any"
 | 
			
		||||
                    ) * 100
 | 
			
		||||
                ) / 100;
 | 
			
		||||
            flopRateUnitPrefix[subCluster.name] =
 | 
			
		||||
                subCluster.flopRateSimd.unit.prefix;
 | 
			
		||||
            flopRateUnitBase[subCluster.name] =
 | 
			
		||||
                subCluster.flopRateSimd.unit.base;
 | 
			
		||||
            memBwRate[subCluster.name] =
 | 
			
		||||
                Math.floor(
 | 
			
		||||
                    sumUp(
 | 
			
		||||
                        $mainQuery.data.nodeMetrics,
 | 
			
		||||
                        subCluster.name,
 | 
			
		||||
                        "mem_bw"
 | 
			
		||||
                    ) * 100
 | 
			
		||||
                ) / 100;
 | 
			
		||||
            memBwRateUnitPrefix[subCluster.name] =
 | 
			
		||||
                subCluster.memoryBandwidth.unit.prefix;
 | 
			
		||||
            memBwRateUnitBase[subCluster.name] =
 | 
			
		||||
                subCluster.memoryBandwidth.unit.base;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const updateConfigurationMutation = ({ name, value }) => {
 | 
			
		||||
        return mutationStore({
 | 
			
		||||
            client: client,
 | 
			
		||||
            query: gql`
 | 
			
		||||
                mutation ($name: String!, $value: String!) {
 | 
			
		||||
                    updateConfiguration(name: $name, value: $value)
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
            variables: { name, value },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function updateTopUserConfiguration(select) {
 | 
			
		||||
        if (
 | 
			
		||||
            ccconfig[`status_view_selectedTopUserCategory:${cluster}`] != select
 | 
			
		||||
        ) {
 | 
			
		||||
            updateConfigurationMutation({
 | 
			
		||||
                name: `status_view_selectedTopUserCategory:${cluster}`,
 | 
			
		||||
                value: JSON.stringify(select),
 | 
			
		||||
            }).subscribe((res) => {
 | 
			
		||||
                if (res.fetching === false && !res.error) {
 | 
			
		||||
                    // console.log(`status_view_selectedTopUserCategory:${cluster}` + ' -> Updated!')
 | 
			
		||||
                } else if (res.fetching === false && res.error) {
 | 
			
		||||
                    throw res.error;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // console.log('No Mutation Required: Top User')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function updateTopProjectConfiguration(select) {
 | 
			
		||||
        if (
 | 
			
		||||
            ccconfig[`status_view_selectedTopProjectCategory:${cluster}`] !=
 | 
			
		||||
            select
 | 
			
		||||
        ) {
 | 
			
		||||
            updateConfigurationMutation({
 | 
			
		||||
                name: `status_view_selectedTopProjectCategory:${cluster}`,
 | 
			
		||||
                value: JSON.stringify(select),
 | 
			
		||||
            }).subscribe((res) => {
 | 
			
		||||
                if (res.fetching === false && !res.error) {
 | 
			
		||||
                    // console.log(`status_view_selectedTopProjectCategory:${cluster}` + ' -> Updated!')
 | 
			
		||||
                } else if (res.fetching === false && res.error) {
 | 
			
		||||
                    throw res.error;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // console.log('No Mutation Required: Top Project')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $: updateTopUserConfiguration(topUserSelection.key);
 | 
			
		||||
    $: updateTopProjectConfiguration(topProjectSelection.key);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- Loading indicator & Refresh -->
 | 
			
		||||
 | 
			
		||||
<Row>
 | 
			
		||||
    <Col xs="auto" style="align-self: flex-end;">
 | 
			
		||||
        <h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
 | 
			
		||||
    </Col>
 | 
			
		||||
    <Col xs="auto"  style="margin-left: 0.25rem;">
 | 
			
		||||
        {#if $initq.fetching || $mainQuery.fetching}
 | 
			
		||||
            <Spinner />
 | 
			
		||||
        {:else if $initq.error}
 | 
			
		||||
            <Card body color="danger">{$initq.error.message}</Card>
 | 
			
		||||
        {:else}
 | 
			
		||||
            <!-- ... -->
 | 
			
		||||
        {/if}
 | 
			
		||||
    </Col>
 | 
			
		||||
    <Col xs="auto" style="margin-left: auto;">
 | 
			
		||||
        <Button
 | 
			
		||||
            outline color="secondary"
 | 
			
		||||
            on:click={() => (isHistogramSelectionOpen = true)}>
 | 
			
		||||
            <Icon name="bar-chart-line"/> Select Histograms
 | 
			
		||||
        </Button>
 | 
			
		||||
    </Col>
 | 
			
		||||
    <Col xs="auto" style="margin-left: 0.25rem;">
 | 
			
		||||
        <Refresher
 | 
			
		||||
            initially={120}
 | 
			
		||||
            on:reload={() => {
 | 
			
		||||
                from = new Date(Date.now() - 5 * 60 * 1000);
 | 
			
		||||
                to = new Date(Date.now());
 | 
			
		||||
            }}
 | 
			
		||||
        />
 | 
			
		||||
    </Col>
 | 
			
		||||
</Row>
 | 
			
		||||
{#if $mainQuery.error}
 | 
			
		||||
    <Row>
 | 
			
		||||
        <Col>
 | 
			
		||||
            <Card body color="danger">{$mainQuery.error.message}</Card>
 | 
			
		||||
        </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
<hr />
 | 
			
		||||
 | 
			
		||||
<!-- Gauges & Roofline per Subcluster-->
 | 
			
		||||
 | 
			
		||||
{#if $initq.data && $mainQuery.data}
 | 
			
		||||
    {#each $initq.data.clusters.find((c) => c.name == cluster).subClusters as subCluster, i}
 | 
			
		||||
        <Row class="mb-3 justify-content-center">
 | 
			
		||||
            <Col md="4" class="px-3">
 | 
			
		||||
                <Card class="h-auto mt-1">
 | 
			
		||||
                    <CardHeader>
 | 
			
		||||
                        <CardTitle class="mb-0"
 | 
			
		||||
                            >SubCluster "{subCluster.name}"</CardTitle
 | 
			
		||||
                        >
 | 
			
		||||
                    </CardHeader>
 | 
			
		||||
                    <CardBody>
 | 
			
		||||
                        <Table borderless>
 | 
			
		||||
                            <tr class="py-2">
 | 
			
		||||
                                <th scope="col">Allocated Nodes</th>
 | 
			
		||||
                                <td style="min-width: 100px;"
 | 
			
		||||
                                    ><div class="col">
 | 
			
		||||
                                        <Progress
 | 
			
		||||
                                            value={allocatedNodes[
 | 
			
		||||
                                                subCluster.name
 | 
			
		||||
                                            ]}
 | 
			
		||||
                                            max={subCluster.numberOfNodes}
 | 
			
		||||
                                        />
 | 
			
		||||
                                    </div></td
 | 
			
		||||
                                >
 | 
			
		||||
                                <td
 | 
			
		||||
                                    >{allocatedNodes[subCluster.name]} / {subCluster.numberOfNodes}
 | 
			
		||||
                                    Nodes</td
 | 
			
		||||
                                >
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr class="py-2">
 | 
			
		||||
                                <th scope="col"
 | 
			
		||||
                                    >Flop Rate (Any) <Icon
 | 
			
		||||
                                        name="info-circle"
 | 
			
		||||
                                        class="p-1"
 | 
			
		||||
                                        style="cursor: help;"
 | 
			
		||||
                                        title="Flops[Any] = (Flops[Double] x 2) + Flops[Single]"
 | 
			
		||||
                                    /></th
 | 
			
		||||
                                >
 | 
			
		||||
                                <td style="min-width: 100px;"
 | 
			
		||||
                                    ><div class="col">
 | 
			
		||||
                                        <Progress
 | 
			
		||||
                                            value={flopRate[subCluster.name]}
 | 
			
		||||
                                            max={subCluster.flopRateSimd.value *
 | 
			
		||||
                                                subCluster.numberOfNodes}
 | 
			
		||||
                                        />
 | 
			
		||||
                                    </div></td
 | 
			
		||||
                                >
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    {scaleNumbers(
 | 
			
		||||
                                        flopRate[subCluster.name],
 | 
			
		||||
                                        subCluster.flopRateSimd.value *
 | 
			
		||||
                                            subCluster.numberOfNodes,
 | 
			
		||||
                                        flopRateUnitPrefix[subCluster.name]
 | 
			
		||||
                                    )}{flopRateUnitBase[subCluster.name]} [Max]
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr class="py-2">
 | 
			
		||||
                                <th scope="col">MemBw Rate</th>
 | 
			
		||||
                                <td style="min-width: 100px;"
 | 
			
		||||
                                    ><div class="col">
 | 
			
		||||
                                        <Progress
 | 
			
		||||
                                            value={memBwRate[subCluster.name]}
 | 
			
		||||
                                            max={subCluster.memoryBandwidth
 | 
			
		||||
                                                .value *
 | 
			
		||||
                                                subCluster.numberOfNodes}
 | 
			
		||||
                                        />
 | 
			
		||||
                                    </div></td
 | 
			
		||||
                                >
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    {scaleNumbers(
 | 
			
		||||
                                        memBwRate[subCluster.name],
 | 
			
		||||
                                        subCluster.memoryBandwidth.value *
 | 
			
		||||
                                            subCluster.numberOfNodes,
 | 
			
		||||
                                        memBwRateUnitPrefix[subCluster.name]
 | 
			
		||||
                                    )}{memBwRateUnitBase[subCluster.name]} [Max]
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </Table>
 | 
			
		||||
                    </CardBody>
 | 
			
		||||
                </Card>
 | 
			
		||||
            </Col>
 | 
			
		||||
            <Col class="px-3">
 | 
			
		||||
                <div bind:clientWidth={plotWidths[i]}>
 | 
			
		||||
                    {#key $mainQuery.data.nodeMetrics}
 | 
			
		||||
                        <Roofline
 | 
			
		||||
                            allowSizeChange={true}
 | 
			
		||||
                            width={plotWidths[i] - 10}
 | 
			
		||||
                            height={300}
 | 
			
		||||
                            cluster={subCluster}
 | 
			
		||||
                            data={
 | 
			
		||||
                                transformPerNodeDataForRoofline(
 | 
			
		||||
                                    $mainQuery.data.nodeMetrics.filter(
 | 
			
		||||
                                        (data) => data.subCluster == subCluster.name
 | 
			
		||||
                                    )
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        />
 | 
			
		||||
                    {/key}
 | 
			
		||||
                </div>
 | 
			
		||||
            </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
    {/each}
 | 
			
		||||
 | 
			
		||||
    <hr/>
 | 
			
		||||
 | 
			
		||||
    <!-- Usage Stats as Histograms -->
 | 
			
		||||
 | 
			
		||||
    <Row>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            <div bind:clientWidth={colWidth1}>
 | 
			
		||||
                <h4 class="text-center">
 | 
			
		||||
                    Top Users on {cluster.charAt(0).toUpperCase() +
 | 
			
		||||
                        cluster.slice(1)}
 | 
			
		||||
                </h4>
 | 
			
		||||
                {#key $topUserQuery.data}
 | 
			
		||||
                    {#if $topUserQuery.fetching}
 | 
			
		||||
                        <Spinner />
 | 
			
		||||
                    {:else if $topUserQuery.error}
 | 
			
		||||
                        <Card body color="danger"
 | 
			
		||||
                            >{$topUserQuery.error.message}</Card
 | 
			
		||||
                        >
 | 
			
		||||
                    {:else}
 | 
			
		||||
                        <Pie
 | 
			
		||||
                            size={colWidth1}
 | 
			
		||||
                            sliceLabel={topUserSelection.label}
 | 
			
		||||
                            quantities={$topUserQuery.data.topUser.map(
 | 
			
		||||
                                (tu) => tu[topUserSelection.key]
 | 
			
		||||
                            )}
 | 
			
		||||
                            entities={$topUserQuery.data.topUser.map(
 | 
			
		||||
                                (tu) => tu.id
 | 
			
		||||
                            )}
 | 
			
		||||
                        />
 | 
			
		||||
                    {/if}
 | 
			
		||||
                {/key}
 | 
			
		||||
            </div>
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col class="px-4 py-2">
 | 
			
		||||
            {#key $topUserQuery.data}
 | 
			
		||||
                {#if $topUserQuery.fetching}
 | 
			
		||||
                    <Spinner />
 | 
			
		||||
                {:else if $topUserQuery.error}
 | 
			
		||||
                    <Card body color="danger"
 | 
			
		||||
                        >{$topUserQuery.error.message}</Card
 | 
			
		||||
                    >
 | 
			
		||||
                {:else}
 | 
			
		||||
                    <Table>
 | 
			
		||||
                        <tr class="mb-2">
 | 
			
		||||
                            <th>Legend</th>
 | 
			
		||||
                            <th>User Name</th>
 | 
			
		||||
                            <th
 | 
			
		||||
                                >Number of
 | 
			
		||||
                                <select
 | 
			
		||||
                                    class="p-0"
 | 
			
		||||
                                    bind:value={topUserSelection}
 | 
			
		||||
                                >
 | 
			
		||||
                                    {#each topOptions as option}
 | 
			
		||||
                                        <option value={option}>
 | 
			
		||||
                                            {option.label}
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                    {/each}
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        {#each $topUserQuery.data.topUser as tu, i}
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td
 | 
			
		||||
                                    ><Icon
 | 
			
		||||
                                        name="circle-fill"
 | 
			
		||||
                                        style="color: {colors[i]};"
 | 
			
		||||
                                    /></td
 | 
			
		||||
                                >
 | 
			
		||||
                                <th scope="col"
 | 
			
		||||
                                    ><a
 | 
			
		||||
                                        href="/monitoring/user/{tu.id}?cluster={cluster}&state=running"
 | 
			
		||||
                                        >{tu.id}</a
 | 
			
		||||
                                    ></th
 | 
			
		||||
                                >
 | 
			
		||||
                                <td>{tu[topUserSelection.key]}</td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        {/each}
 | 
			
		||||
                    </Table>
 | 
			
		||||
                {/if}
 | 
			
		||||
            {/key}
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            <h4 class="text-center">
 | 
			
		||||
                Top Projects on {cluster.charAt(0).toUpperCase() +
 | 
			
		||||
                    cluster.slice(1)}
 | 
			
		||||
            </h4>
 | 
			
		||||
            {#key $topProjectQuery.data}
 | 
			
		||||
                {#if $topProjectQuery.fetching}
 | 
			
		||||
                    <Spinner />
 | 
			
		||||
                {:else if $topProjectQuery.error}
 | 
			
		||||
                    <Card body color="danger"
 | 
			
		||||
                        >{$topProjectQuery.error.message}</Card
 | 
			
		||||
                    >
 | 
			
		||||
                {:else}
 | 
			
		||||
                    <Pie
 | 
			
		||||
                        size={colWidth1}
 | 
			
		||||
                        sliceLabel={topProjectSelection.label}
 | 
			
		||||
                        quantities={$topProjectQuery.data.topProjects.map(
 | 
			
		||||
                            (tp) => tp[topProjectSelection.key]
 | 
			
		||||
                        )}
 | 
			
		||||
                        entities={$topProjectQuery.data.topProjects.map(
 | 
			
		||||
                            (tp) => tp.id
 | 
			
		||||
                        )}
 | 
			
		||||
                    />
 | 
			
		||||
                {/if}
 | 
			
		||||
            {/key}
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col class="px-4 py-2">
 | 
			
		||||
            {#key $topProjectQuery.data}
 | 
			
		||||
                {#if $topProjectQuery.fetching}
 | 
			
		||||
                    <Spinner />
 | 
			
		||||
                {:else if $topProjectQuery.error}
 | 
			
		||||
                    <Card body color="danger"
 | 
			
		||||
                        >{$topProjectQuery.error.message}</Card
 | 
			
		||||
                    >
 | 
			
		||||
                {:else}
 | 
			
		||||
                    <Table>
 | 
			
		||||
                        <tr class="mb-2">
 | 
			
		||||
                            <th>Legend</th>
 | 
			
		||||
                            <th>Project Code</th>
 | 
			
		||||
                            <th
 | 
			
		||||
                                >Number of
 | 
			
		||||
                                <select
 | 
			
		||||
                                    class="p-0"
 | 
			
		||||
                                    bind:value={topProjectSelection}
 | 
			
		||||
                                >
 | 
			
		||||
                                    {#each topOptions as option}
 | 
			
		||||
                                        <option value={option}>
 | 
			
		||||
                                            {option.label}
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                    {/each}
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        {#each $topProjectQuery.data.topProjects as tp, i}
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td
 | 
			
		||||
                                    ><Icon
 | 
			
		||||
                                        name="circle-fill"
 | 
			
		||||
                                        style="color: {colors[i]};"
 | 
			
		||||
                                    /></td
 | 
			
		||||
                                >
 | 
			
		||||
                                <th scope="col"
 | 
			
		||||
                                    ><a
 | 
			
		||||
                                        href="/monitoring/jobs/?cluster={cluster}&state=running&project={tp.id}&projectMatch=eq"
 | 
			
		||||
                                        >{tp.id}</a
 | 
			
		||||
                                    ></th
 | 
			
		||||
                                >
 | 
			
		||||
                                <td>{tp[topProjectSelection.key]}</td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        {/each}
 | 
			
		||||
                    </Table>
 | 
			
		||||
                {/if}
 | 
			
		||||
            {/key}
 | 
			
		||||
        </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
    <hr class="my-2" />
 | 
			
		||||
    <Row>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            <div bind:clientWidth={colWidth2}>
 | 
			
		||||
                {#key $mainQuery.data.stats}
 | 
			
		||||
                    <Histogram
 | 
			
		||||
                        data={convert2uplot(
 | 
			
		||||
                            $mainQuery.data.stats[0].histDuration
 | 
			
		||||
                        )}
 | 
			
		||||
                        width={colWidth2 - 25}
 | 
			
		||||
                        title="Duration Distribution"
 | 
			
		||||
                        xlabel="Current Runtimes"
 | 
			
		||||
                        xunit="Hours"
 | 
			
		||||
                        ylabel="Number of Jobs"
 | 
			
		||||
                        yunit="Jobs"
 | 
			
		||||
                    />
 | 
			
		||||
                {/key}
 | 
			
		||||
            </div>
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            {#key $mainQuery.data.stats}
 | 
			
		||||
                <Histogram
 | 
			
		||||
                    data={convert2uplot($mainQuery.data.stats[0].histNumNodes)}
 | 
			
		||||
                    width={colWidth2 - 25}
 | 
			
		||||
                    title="Number of Nodes Distribution"
 | 
			
		||||
                    xlabel="Allocated Nodes"
 | 
			
		||||
                    xunit="Nodes"
 | 
			
		||||
                    ylabel="Number of Jobs"
 | 
			
		||||
                    yunit="Jobs"
 | 
			
		||||
                />
 | 
			
		||||
            {/key}
 | 
			
		||||
        </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
    <Row cols={2}>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            <div bind:clientWidth={colWidth2}>
 | 
			
		||||
                {#key $mainQuery.data.stats}
 | 
			
		||||
                    <Histogram
 | 
			
		||||
                        data={convert2uplot(
 | 
			
		||||
                            $mainQuery.data.stats[0].histNumCores
 | 
			
		||||
                        )}
 | 
			
		||||
                        width={colWidth2 - 25}
 | 
			
		||||
                        title="Number of Cores Distribution"
 | 
			
		||||
                        xlabel="Allocated Cores"
 | 
			
		||||
                        xunit="Cores"
 | 
			
		||||
                        ylabel="Number of Jobs"
 | 
			
		||||
                        yunit="Jobs"
 | 
			
		||||
                    />
 | 
			
		||||
                {/key}
 | 
			
		||||
            </div>
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col class="p-2">
 | 
			
		||||
            {#key $mainQuery.data.stats}
 | 
			
		||||
                <Histogram
 | 
			
		||||
                    data={convert2uplot($mainQuery.data.stats[0].histNumAccs)}
 | 
			
		||||
                    width={colWidth2 - 25}
 | 
			
		||||
                    title="Number of Accelerators Distribution"
 | 
			
		||||
                    xlabel="Allocated Accs"
 | 
			
		||||
                    xunit="Accs"
 | 
			
		||||
                    ylabel="Number of Jobs"
 | 
			
		||||
                    yunit="Jobs"
 | 
			
		||||
                />
 | 
			
		||||
            {/key}
 | 
			
		||||
        </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
    <hr class="my-2" />
 | 
			
		||||
    {#if metricsInHistograms}
 | 
			
		||||
        <Row>
 | 
			
		||||
            <Col>
 | 
			
		||||
                {#key $mainQuery.data.stats[0].histMetrics}
 | 
			
		||||
                    <PlotTable
 | 
			
		||||
                        let:item
 | 
			
		||||
                        let:width
 | 
			
		||||
                        renderFor="user"
 | 
			
		||||
                        items={$mainQuery.data.stats[0].histMetrics}
 | 
			
		||||
                        itemsPerRow={3}>
 | 
			
		||||
 | 
			
		||||
                        <Histogram
 | 
			
		||||
                            data={convert2uplot(item.data)}
 | 
			
		||||
                            usesBins={true}
 | 
			
		||||
                            width={width} height={250}
 | 
			
		||||
                            title="Distribution of '{item.metric}' averages"
 | 
			
		||||
                            xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`}
 | 
			
		||||
                            xunit={item.unit}
 | 
			
		||||
                            ylabel="Number of Jobs"
 | 
			
		||||
                            yunit="Jobs"/>
 | 
			
		||||
                    </PlotTable>
 | 
			
		||||
                {/key}
 | 
			
		||||
            </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
    {/if}
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
<HistogramSelection
 | 
			
		||||
bind:cluster={cluster}
 | 
			
		||||
bind:metricsInHistograms={metricsInHistograms}
 | 
			
		||||
bind:isOpen={isHistogramSelectionOpen} />
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {} from './header.entrypoint.js'
 | 
			
		||||
import Status from './Status.root.svelte'
 | 
			
		||||
import Status from './Control.root.svelte'
 | 
			
		||||
 | 
			
		||||
new Status({
 | 
			
		||||
    target: document.getElementById('svelte-app'),
 | 
			
		||||
							
								
								
									
										70
									
								
								web/frontend/src/partition/ClusterMachine.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								web/frontend/src/partition/ClusterMachine.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
<script>
 | 
			
		||||
  import {
 | 
			
		||||
    Row,
 | 
			
		||||
    Col,
 | 
			
		||||
    Card,
 | 
			
		||||
    CardBody,
 | 
			
		||||
    CardTitle,
 | 
			
		||||
    Button,
 | 
			
		||||
    Modal,
 | 
			
		||||
    ModalHeader,
 | 
			
		||||
    ModalBody,
 | 
			
		||||
    ModalFooter,
 | 
			
		||||
    Input,
 | 
			
		||||
  } from "sveltestrap";
 | 
			
		||||
 | 
			
		||||
  let isModalOpen = false;
 | 
			
		||||
  let selectedCard = null;
 | 
			
		||||
  let search = "";
 | 
			
		||||
 | 
			
		||||
  const toggleModal = (card) => {
 | 
			
		||||
    selectedCard = card;
 | 
			
		||||
    isModalOpen = !isModalOpen;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let cards = [
 | 
			
		||||
    {
 | 
			
		||||
      title: "Card 1",
 | 
			
		||||
      content: "This is the first card.",
 | 
			
		||||
      modalContent: "This is the first modal.",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Card 2",
 | 
			
		||||
      content: "This is the second card.",
 | 
			
		||||
      modalContent: "This is the second modal.",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Card 3",
 | 
			
		||||
      content: "This is the third card.",
 | 
			
		||||
      modalContent: "This is the third modal.",
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<Input type="search" placeholder="Search..." bind:value={search} class="mb-2"/>
 | 
			
		||||
<Row>
 | 
			
		||||
  {#each cards.filter((card) => card.title
 | 
			
		||||
        .toLowerCase()
 | 
			
		||||
        .includes(search.toLowerCase()) || card.content
 | 
			
		||||
        .toLowerCase()
 | 
			
		||||
        .includes(search.toLowerCase())) as card (card.title)}
 | 
			
		||||
    <Col sm={12} md={6} lg={4}>
 | 
			
		||||
      <Card class="m-2">
 | 
			
		||||
        <CardBody>
 | 
			
		||||
          <CardTitle>{card.title}</CardTitle>
 | 
			
		||||
          <p>{card.content}</p>
 | 
			
		||||
          <Button on:click={() => toggleModal(card)}>Open Modal</Button>
 | 
			
		||||
        </CardBody>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </Col>
 | 
			
		||||
  {/each}
 | 
			
		||||
</Row>
 | 
			
		||||
 | 
			
		||||
<Modal isOpen={isModalOpen} toggle={toggleModal}>
 | 
			
		||||
  <ModalHeader toggle={toggleModal}>Modal Title</ModalHeader>
 | 
			
		||||
  <ModalBody>{selectedCard ? selectedCard.modalContent : ""}</ModalBody>
 | 
			
		||||
  <ModalFooter>
 | 
			
		||||
    <Button color="secondary" on:click={toggleModal}>Close</Button>
 | 
			
		||||
  </ModalFooter>
 | 
			
		||||
</Modal>
 | 
			
		||||
							
								
								
									
										59
									
								
								web/frontend/src/partition/LinuxUser.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								web/frontend/src/partition/LinuxUser.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import { Table, Input, Button } from "sveltestrap";
 | 
			
		||||
 | 
			
		||||
    let search = "";
 | 
			
		||||
    let data = [
 | 
			
		||||
        {
 | 
			
		||||
            username: "User1",
 | 
			
		||||
            organization: "Org1",
 | 
			
		||||
            fullName: "Full Name 1",
 | 
			
		||||
            lastActive: "2022-01-01",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            username: "User2",
 | 
			
		||||
            organization: "Org2",
 | 
			
		||||
            fullName: "Full Name 2",
 | 
			
		||||
            lastActive: "2022-01-02",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            username: "User3",
 | 
			
		||||
            organization: "Org3",
 | 
			
		||||
            fullName: "Full Name 3",
 | 
			
		||||
            lastActive: "2022-01-03",
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Button color="primary" class="mb-3">New User</Button>
 | 
			
		||||
<Input type="search" placeholder="Search..." bind:value={search} class="mb-3" />
 | 
			
		||||
 | 
			
		||||
<Table striped>
 | 
			
		||||
    <thead>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <th>Username</th>
 | 
			
		||||
            <th>Linux-machine</th>
 | 
			
		||||
            <th>Usage</th>
 | 
			
		||||
            <th>Role</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
        {#each data.filter((item) => Object.values(item).some((value) => value
 | 
			
		||||
                    .toLowerCase()
 | 
			
		||||
                    .includes(search.toLowerCase()))) as item (item.username)}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class=""
 | 
			
		||||
                    ><p class="font-weight-light">{item.username}</p></td
 | 
			
		||||
                >
 | 
			
		||||
                <td class="bg-light">{item.linuxMachine}</td>
 | 
			
		||||
                <td class="bg-light">{item.usage}</td>
 | 
			
		||||
                <td class="bg-light">{item.role}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        {/each}
 | 
			
		||||
    </tbody>
 | 
			
		||||
</Table>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    .bg-light {
 | 
			
		||||
        background-color: #f8f9fa; /* This is the color Bootstrap uses for .bg-light */
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,24 +1,26 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import { TabContent, TabPane } from 'sveltestrap';
 | 
			
		||||
  
 | 
			
		||||
</script>
 | 
			
		||||
  
 | 
			
		||||
  <TabContent vertical pills >
 | 
			
		||||
    <TabPane tabId="alpha" tab="Alpha" active>
 | 
			
		||||
      <h2>Alpha</h2>
 | 
			
		||||
      <img
 | 
			
		||||
        alt="Alpha Flight"
 | 
			
		||||
        src="https://upload.wikimedia.org/wikipedia/en/4/49/Alpha_Flight_cast_picture_%28John_Byrne_era%29.gif"
 | 
			
		||||
      />
 | 
			
		||||
    </TabPane>
 | 
			
		||||
    <TabPane tabId="bravo" tab="Bravo">
 | 
			
		||||
      <h2>Bravo</h2>
 | 
			
		||||
      <img
 | 
			
		||||
        alt="Johnny Bravo"
 | 
			
		||||
        src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Johnny_Bravo_series_logo.png/320px-Johnny_Bravo_series_logo.png"
 | 
			
		||||
      />
 | 
			
		||||
    </TabPane>
 | 
			
		||||
    <TabPane tabId="charlie" tab="Charlie">
 | 
			
		||||
      <h2>Charlie</h2>
 | 
			
		||||
      <img alt="Charlie Brown" src="https://upload.wikimedia.org/wikipedia/en/2/22/Charlie_Brown.png" />
 | 
			
		||||
    </TabPane>
 | 
			
		||||
<script>
 | 
			
		||||
  import { Icon, TabContent, TabPane } from "sveltestrap";
 | 
			
		||||
    import LinuxUser from "./LinuxUser.svelte";
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<TabContent>
 | 
			
		||||
  <TabPane tabId="local-user" active class="mt-3">
 | 
			
		||||
    <span slot="tab">
 | 
			
		||||
      Local User
 | 
			
		||||
      <!-- <Icon name="gear" /> -->
 | 
			
		||||
    </span>
 | 
			
		||||
  </TabPane>
 | 
			
		||||
  <TabPane tabId="linux-user" class="mt-3">
 | 
			
		||||
    <span slot="tab">
 | 
			
		||||
      Linux User
 | 
			
		||||
      <!-- <Icon name="hand-thumbs-up" /> -->
 | 
			
		||||
    </span>
 | 
			
		||||
    <LinuxUser />
 | 
			
		||||
  </TabPane>
 | 
			
		||||
  <TabPane tabId="group" class="mt-3">
 | 
			
		||||
    <span slot="tab">
 | 
			
		||||
      Group
 | 
			
		||||
      <!-- <Icon name="alarm" /> -->
 | 
			
		||||
    </span>
 | 
			
		||||
  </TabPane>
 | 
			
		||||
</TabContent>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
{{define "stylesheets"}}
 | 
			
		||||
    <link rel='stylesheet' href='/build/status.css'>
 | 
			
		||||
    <link rel='stylesheet' href='/build/control.css'>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{define "javascript"}}
 | 
			
		||||
    <script>
 | 
			
		||||
        const infos = {{ .Infos }};
 | 
			
		||||
        const clusterCockpitConfig = {{ .Config }};
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src='/build/status.js'></script>
 | 
			
		||||
    <script src='/build/control.js'></script>
 | 
			
		||||
{{end}}
 | 
			
		||||
		Reference in New Issue
	
	Block a user