Newer
Older
smart-home-server / webclient-vue / src / features / devices / pages / DevicesScanningPage.vue
@Eugene Sukhodolskiy Eugene Sukhodolskiy 23 hours ago 5 KB Add script detail pages with scope grouping
<template>
  <section class="page">
    <GnPageHeader title="Scanning" kicker="Devices">
      <template #actions>
        <div class="devices-actions">
          <GnButton
            :variant="scanningStore.mode === 'setup' ? 'primary' : 'secondary'"
            @click="setMode('setup')"
          >
            Setup
          </GnButton>
          <GnButton
            :variant="scanningStore.mode === 'all' ? 'primary' : 'secondary'"
            @click="setMode('all')"
          >
            All
          </GnButton>
          <GnButton
            :loading="scanningStore.isLoading"
            icon="ph-magnifying-glass"
            @click="scan"
          >
            Scan
          </GnButton>
        </div>
      </template>
    </GnPageHeader>

    <AppLoadingState v-if="scanningStore.isLoading" text="Scanning network" />

    <AppErrorState
      v-else-if="scanningStore.error"
      title="Scan failed"
      :message="scanningStore.error.message"
      :retry="scan"
    />

    <AppEmptyState
      v-else-if="scanningStore.devices.length === 0"
      title="No devices found"
      message="Choose scan mode and click Scan to discover devices."
    />

    <div v-else class="devices-panel">
      <div class="devices-summary">
        <GnBadge variant="primary">{{ scanningStore.total }} found</GnBadge>
        <GnBadge variant="secondary">Mode: {{ scanningStore.mode }}</GnBadge>
      </div>

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

        <template #cell-status="{ row }">
          <GnBadge :variant="row.status === 'setup' ? 'warning' : 'success'">{{ row.status }}</GnBadge>
        </template>

        <template #cell-actions="{ row }">
          <GnButton
            v-if="row.status === 'setup'"
            variant="primary"
            icon="ph-plus"
            @click="openSetup(row)"
          >
            Add
          </GnButton>
        </template>
      </GnTable>
    </div>

    <GnModal
      :open="showSetupModal"
      title="Setup new device"
      @update:open="showSetupModal = $event"
    >
      <div class="form-group">
        <GnInput v-model="setupForm.alias" label="Alias" placeholder="kitchen_relay" />
      </div>
      <div class="form-group">
        <GnInput v-model="setupForm.name" label="Name" placeholder="Kitchen Relay" />
      </div>
      <div class="form-group">
        <GnInput v-model="setupForm.description" label="Description" />
      </div>
      <div v-if="setupError" class="form-group">
        <GnAlert variant="danger">{{ setupError }}</GnAlert>
      </div>
      <template #footer>
        <GnButton variant="secondary" @click="showSetupModal = false">Cancel</GnButton>
        <GnButton variant="primary" icon="ph-plus" :loading="setupLoading" @click="submitSetup">
          Add device
        </GnButton>
      </template>
    </GnModal>
  </section>
</template>

<script setup>
import { ref, reactive, computed } from "vue";
import { useScanningStore } from "../../../stores/scanning";
import {
  GnPageHeader,
  GnButton,
  GnBadge,
  GnTable,
  GnModal,
  GnInput,
  GnAlert,
} from "gnexus-ui-kit/vue";
import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
import AppErrorState from "../../../components/feedback/AppErrorState.vue";
import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";

const scanningStore = useScanningStore();

const showSetupModal = ref(false);
const setupLoading = ref(false);
const setupError = ref("");
const setupForm = reactive({
  device_ip: "",
  alias: "",
  name: "",
  description: "",
});

const tableColumns = [
  { key: "device_name", label: "Device" },
  { key: "ip_address", label: "IP" },
  { key: "mac_address", label: "MAC" },
  { key: "firmware_version", label: "Firmware" },
  { key: "status", label: "Status" },
  { key: "actions", label: "Actions" },
];

const tableRows = computed(() =>
  scanningStore.devices.map((device) => ({
    id: device.device_id || device.ip_address,
    device_name: device.device_name || "Unknown",
    device_type: device.device_type || "unknown",
    ip_address: device.ip_address || "unknown",
    mac_address: device.mac_address || "unknown",
    firmware_version: device.firmware_version || "unknown",
    status: device.status || "unknown",
  }))
);

function setMode(mode) {
  scanningStore.setMode(mode);
}

function scan() {
  scanningStore.scan();
}

function openSetup(row) {
  setupForm.device_ip = row.ip_address;
  setupForm.alias = "";
  setupForm.name = "";
  setupForm.description = "";
  setupError.value = "";
  showSetupModal.value = true;
}

async function submitSetup() {
  setupLoading.value = true;
  setupError.value = "";

  const result = await scanningStore.setupDevice({ ...setupForm });
  setupLoading.value = false;

  if (!result.ok) {
    setupError.value = result.error?.message || "Failed to setup device";
    return;
  }

  showSetupModal.value = false;
}
</script>

<style scoped>
.form-group {
  margin-bottom: 16px;
}
</style>