Newer
Older
smart-home-server / webclient-vue / src / features / scripts / pages / ScriptsActionsPage.vue
<template>
  <section class="page">
    <GnPageHeader title="Actions" kicker="Scripts">
      <template #actions>
        <GnBadge variant="primary">{{ scriptsStore.totalActions }} scripts</GnBadge>
      </template>
    </GnPageHeader>

    <AppLoadingState v-if="scriptsStore.isLoadingActions" text="Loading actions" />

    <AppErrorState
      v-else-if="scriptsStore.errorActions"
      title="Actions loading failed"
      :error="scriptsStore.errorActions"
      :retry="scriptsStore.loadActions"
    />

    <AppEmptyState
      v-else-if="scriptsStore.actions.length === 0"
      title="No action scripts"
      message="No action scripts registered."
    />

    <div v-else class="area-grid">
      <GnActionCard
        v-for="script in scriptsStore.actions"
        :key="script.alias"
        :title="script.name"
        @click="goToDetail(script.alias)"
      >
        <template #default>
          <div v-html="script.icon" class="script-icon" />
          <p v-if="script.description">{{ script.description }}</p>
          <div class="script-meta">
            <GnBadge :variant="script.state === 'enabled' ? 'success' : 'secondary'"
            >{{ script.state }}</GnBadge>
            <GnBadge v-if="areaFor(script)" variant="primary">{{ areaFor(script).display_name }}</GnBadge>
          </div>
          <small>{{ script.author }}</small>
        </template>
        <template #actions>
          <GnButton
            variant="primary"
            icon="ph-play"
            :loading="scriptsStore.isRunning(script.alias)"
            :disabled="script.state !== 'enabled'"
            @click.stop="run(script.alias)"
          >
            Run
          </GnButton>
        </template>
      </GnActionCard>
    </div>
  </section>
</template>

<script setup>
import { onMounted } from "vue";
import { useRouter } from "vue-router";
import { useScriptsStore } from "../../../stores/scripts";
import { useAreasStore } from "../../../stores/areas";
import {
  GnPageHeader,
  GnBadge,
  GnActionCard,
  GnButton,
  useToast,
} 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 router = useRouter();
const scriptsStore = useScriptsStore();
const areasStore = useAreasStore();
const toast = useToast();

function areaFor(script) {
  if (!script.area_id) return null;
  return areasStore.areas.find((a) => a.id === script.area_id) || null;
}

async function run(alias) {
  const result = await scriptsStore.runScript(alias);
  if (result?.ok) {
    toast.success({ title: `Ran ${alias}`, text: result.execTime ? `Exec time: ${result.execTime}` : undefined });
  } else {
    toast.error({ title: `Failed ${alias}`, text: result?.error?.message || "Unknown error" });
  }
}

function goToDetail(alias) {
  router.push({ name: "script-detail", params: { type: "actions", id: alias } });
}

onMounted(() => {
  scriptsStore.loadActions();
  if (areasStore.areas.length === 0) {
    areasStore.loadAreas();
  }
});
</script>

<style scoped>
.script-icon {
  font-size: 32px;
}

.script-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.result-alert {
  margin-top: 24px;
}

</style>