Newer
Older
smart-home-server / webclient-vue / src / features / devices / pages / DevicesListPage.vue
@Eugene Sukhodolskiy Eugene Sukhodolskiy 23 hours ago 4 KB Add script detail pages with scope grouping
<template>
  <section class="page">
    <GnPageHeader title="Device Matrix" kicker="Devices">
      <template #actions>
        <GnButton
          :loading="devicesStore.isLoading || devicesStore.isLoadingStates"
          icon="ph-arrow-clockwise"
          @click="reload"
        >
          Refresh
        </GnButton>
      </template>
    </GnPageHeader>

    <AppLoadingState v-if="devicesStore.isLoading" text="Loading devices" />

    <AppErrorState
      v-else-if="devicesStore.error"
      title="Devices loading failed"
      :message="devicesStore.error.message"
      :retry="reload"
    />

    <AppEmptyState
      v-else-if="devicesStore.devices.length === 0"
      title="No active devices"
      message="No active devices found."
    />

    <div v-else class="devices-panel">
      <div class="devices-summary">
        <GnBadge variant="primary">Total: {{ devicesStore.total }}</GnBadge>
        <GnBadge v-if="devicesStore.isLoadingStates" variant="secondary">States loading</GnBadge>
        <GnBadge v-else variant="success">States settled</GnBadge>
        <GnBadge v-if="isStale" variant="warning">Stale data</GnBadge>
      </div>

      <AppErrorState
        v-if="devicesStore.stateError"
        title="Device states loading failed"
        :message="devicesStore.stateError.message"
      />

      <GnTable
        :columns="tableColumns"
        :rows="tableRows"
        caption="Registered devices"
      >
        <template #cell-device_name="{ row }">
          <strong>{{ row.device_name }}</strong>
          <small>{{ row.alias }}</small>
        </template>

        <template #cell-connect="{ row }">
          <DeviceConnectionBadge :status="row.connectionStatus" />
        </template>

        <template #cell-state="{ row }">
          <DeviceStateCell :state="row.state" />
        </template>

        <template #cell-actions="{ row }">
          <div class="devices-actions">
            <GnButton variant="secondary" icon="ph-info" disabled>Details</GnButton>
            <GnButton
              variant="warning"
              icon="ph-arrow-clockwise"
              :loading="devicesStore.isRebooting(row.id)"
              @click="reboot(row.id)"
            >
              Reboot
            </GnButton>
          </div>
        </template>
      </GnTable>
    </div>
  </section>
</template>

<script setup>
import { onMounted, computed } from "vue";
import { useDevicesStore } from "../../../stores/devices";
import { GnPageHeader, GnButton, GnBadge, GnTable } from "gnexus-ui-kit/vue";

const STALE_THRESHOLD_MS = 5 * 60 * 1000;
import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
import AppErrorState from "../../../components/feedback/AppErrorState.vue";
import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
import DeviceConnectionBadge from "../components/DeviceConnectionBadge.vue";
import DeviceStateCell from "../components/DeviceStateCell.vue";

const devicesStore = useDevicesStore();

const isStale = computed(() => {
  if (!devicesStore.lastLoadedAt) return false;
  const loaded = new Date(devicesStore.lastLoadedAt).getTime();
  return Date.now() - loaded > STALE_THRESHOLD_MS;
});

const tableColumns = [
  { key: "device_name", label: "Device name" },
  { key: "connect", label: "Connect" },
  { key: "state", label: "State" },
  { key: "ip", label: "IP" },
  { key: "type", label: "Type" },
  { key: "actions", label: "Actions" },
];

const tableRows = computed(() =>
  devicesStore.devices.map((device) => {
    const st = stateFor(device);
    return {
      id: device.id,
      device_name: device.name || device.alias || `Device #${device.id}`,
      alias: device.alias,
      connectionStatus: st.connectionStatus,
      state: st,
      ip: device.device_ip || "unknown",
      type: device.device_type || "unknown",
    };
  })
);

function stateFor(device) {
  return (
    devicesStore.stateByDeviceId[String(device.id)] || {
      status: "idle",
      message: "Not loaded",
      connectionStatus: device.connection_status || "unknown",
    }
  );
}

async function reload() {
  const result = await devicesStore.loadDevices();

  if (result.ok) {
    await devicesStore.loadDeviceStates();
  }
}

function reboot(id) {
  devicesStore.rebootDevice(id);
}

onMounted(reload);
</script>