internal/dashboard/frontend/src/views/ChartView.vue

<script setup lang="ts">
import { IconMap } from "@tabler/icons-vue";
import FilterBar from "components/Filter/FilterBar.vue";
import LiveUpdateButton from "components/LiveUpdateButton.vue";
import { useChartData } from "composables/useChartData";
import { useQuerySync } from "composables/useQuerySync";
import { useChartStore, useUIStore } from "src/store";
import { icons } from "utils/icons";
import { computed, watch } from "vue";
import { useRoute } from "vue-router";

const chartStore = useChartStore();
const route = useRoute();

// Adjusted initialization based on current route
function updateColumns() {
  if (route.path.includes("/map")) {
    chartStore.state.columns = [
      "time",
      "remote_addr",
      "latitude",
      "longitude",
      "country",
      "city",
      "type",
      "event",
    ];
  } else {
    chartStore.state.columns = [
      "time",
      "remote_addr",
      "dst_port",
      "type",
      "event",
    ];
  }
}

updateColumns();
watch(() => route.path, updateColumns);

const uiStore = useUIStore();
const pageStat = computed(() => {
  const total = chartData.value.length;
  const selected = uiStore.selectedCount;
  const selectedIPs = uiStore.selectedIPCount;
  if (selected > 0) {
    return `(${selected} selected, ${selectedIPs} IPs / ${total} events)`;
  }
  return `(${total} events)`;
});

useQuerySync(chartStore.state, {
  limit: { number: true },
  remote_addr: { array: true, comma: true },
  dst_port: { array: true, comma: true },
  time_start: {},
  time_end: {},
  x_range: { array: true, comma: true },
  y_range: { array: true, comma: true },
  domain: { array: true, comma: true },
  fqdn: { array: true, comma: true },
  sync_time_with_chart: { boolean: true },
  sync_ports_with_chart: { boolean: true },
  asn: { array: true, comma: true },
  city: { array: true, comma: true },
  country: { array: true, comma: true },
});

const { chartData, connectionStatus, connect, disconnect, loading } =
  useChartData({
    enabled: () => route.name !== "charts-activity",
  });

function toggleConnection() {
  if (connectionStatus.value === "open") {
    disconnect();
  } else {
    connect();
  }
}
</script>

<template>
  <div class="page-container">
    <div
      class="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 md:gap-x-8"
    >
      <div
        class="text-muted flex flex-wrap items-center gap-x-4 gap-y-4 md:gap-x-8"
      >
        <router-link
          :to="{ path: '/charts/port', query: route.query }"
          class="chart-link"
        >
          <component :is="icons.port" size="24" />
          <span>Port Chart</span>
        </router-link>
        <router-link
          :to="{ path: '/charts/activity', query: route.query }"
          class="chart-link"
        >
          <component :is="icons.activity" size="24" />
          <span>Recent Activity</span>
        </router-link>
        <router-link :to="{ path: '/charts/map' }" class="chart-link">
          <component :is="IconMap" size="24" />
          <span>Threat Map</span>
        </router-link>
        <p class="stat-label" v-if="route.name !== 'charts-activity'">
          {{ pageStat }}
        </p>
      </div>
      <LiveUpdateButton
        :st="connectionStatus"
        @toggleConnection="toggleConnection"
        v-if="route.name !== 'charts-activity'"
      />
    </div>
    <div class="card relative flex flex-col gap-4 md:h-[calc(100vh-9rem)]">
      <div class="flex min-h-8 flex-col gap-4 md:flex-row md:items-start">
        <FilterBar :filters="chartStore.state">
          <template #right>
            <div
              class="flex min-h-9 flex-wrap items-center gap-4 rounded border border-stone-800 bg-stone-900 px-1.5 py-1"
            >
              <div
                id="chart-actions"
                class="flex items-center gap-2 empty:hidden"
              ></div>
              <div class="flex items-center gap-2">
                <span class="filter-label">Max Events:</span>
                <input
                  type="number"
                  v-model.lazy="chartStore.state.limit"
                  class="w-20"
                />
              </div>
            </div>
          </template>
        </FilterBar>
      </div>

      <div class="min-h-0 flex-1">
        <router-view
          :chart-data="chartData"
          :loading="loading"
          :connection-status="connectionStatus"
        />
      </div>
    </div>
  </div>
</template>

<style scoped>
@reference "src/style.css";

.chart-link {
  @apply flex items-center gap-1 whitespace-nowrap no-underline transition-colors;

  span {
    @apply border-b-2 border-transparent transition-colors;
  }

  &:hover {
    @apply text-stone-200;
  }
}

.router-link-active {
  @apply text-white;

  .tabler-icon {
    @apply text-primary-400;
  }

  span {
    @apply border-primary-400;
  }
}
</style>