Newer
Older
smart-home-server / webclient / src / components / script / ActionScriptsGrid.vue
<template>
  <div class="area-grid">
    <div
      v-for="script in scripts"
      :key="script.alias"
      class="action-card-item"
      :class="cardClass(script)"
      @click="goToDetail(script.alias)"
    >
      <GnActionCard :title="script.name">
        <template #default>
          <div v-if="script.icon" 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="script.scope" variant="primary">{{ script.scope }}</GnBadge>
            <GnBadge v-if="showAreaBadge && areaFor(script)" variant="primary">{{ areaFor(script).display_name }}</GnBadge>
            <GnBadge
              v-for="(ind, i) in script.indicators"
              :key="i"
              :variant="ind.variant"
            >{{ ind.label }}</GnBadge>
          </div>
          <small v-if="script.created_by || script.author">{{ script.created_by || script.author }}</small>
        </template>
        <template #actions>
          <GnButton
            v-if="perm.has('scripts.run')"
            variant="primary"
            icon="ph-play"
            :loading="scriptsStore.isRunning(script.alias)"
            :disabled="script.state !== 'enabled'"
            @click.stop="run(script)"
          >
            Run
          </GnButton>
        </template>
      </GnActionCard>
    </div>
  </div>
  <ScriptRunModal
    v-if="activeScript"
    :open="showRunModal"
    :script="activeScript"
    @update:open="showRunModal = $event"
    @run="executeRun($event.alias, $event.params)"
  />
  <GnConfirmDialog
    :open="confirmOpen"
    :title="confirmTitle"
    :message="confirmMessage"
    :confirm-variant="confirmVariant"
    @update:open="confirmOpen = $event"
    @confirm="onConfirmRun"
    @cancel="onCancelConfirm"
  />
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import { useScriptsStore } from "../../stores/scripts";
import { useAreasStore } from "../../stores/areas";
import { usePermission } from "../../composables/usePermission.js";
import ScriptRunModal from "./ScriptRunModal.vue";
import {
  GnBadge,
  GnActionCard,
  GnButton,
  GnConfirmDialog,
  useToast,
} from "gnexus-ui-kit/vue";

const props = defineProps({
  scripts: {
    type: Array,
    required: true,
  },
  showAreaBadge: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits(["run-success"]);

const router = useRouter();
const scriptsStore = useScriptsStore();
const areasStore = useAreasStore();
const toast = useToast();
const perm = usePermission();

const showRunModal = ref(false);
const activeScript = ref(null);

const confirmOpen = ref(false);
const confirmScript = ref(null);
const confirmTitle = ref("");
const confirmMessage = ref("");
const confirmVariant = ref("warning");

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

function cardClass(script) {
  if (script.danger_level === "dangerous") return "card-dangerous";
  if (script.danger_level === "cautious") return "card-cautious";
  return "";
}

function run(script) {
  if (script.danger_level === "cautious" || script.danger_level === "dangerous") {
    confirmScript.value = script;
    confirmTitle.value = `Run: ${script.name || script.alias}`;
    confirmMessage.value =
      script.danger_level === "dangerous"
        ? "This action is marked as dangerous. Are you sure you want to proceed?"
        : "This action requires caution. Proceed?";
    confirmVariant.value = script.danger_level === "dangerous" ? "danger" : "warning";
    confirmOpen.value = true;
    return;
  }

  const schema = script.params_schema;
  if (schema && Object.keys(schema).length > 0) {
    activeScript.value = script;
    showRunModal.value = true;
    return;
  }

  executeRun(script.alias, {});
}

function onConfirmRun() {
  if (confirmScript.value) {
    const schema = confirmScript.value.params_schema;
    if (schema && Object.keys(schema).length > 0) {
      activeScript.value = confirmScript.value;
      showRunModal.value = true;
    } else {
      executeRun(confirmScript.value.alias, {});
    }
    confirmScript.value = null;
  }
}

function onCancelConfirm() {
  confirmScript.value = null;
}

async function executeRun(alias, params) {
  const result = await scriptsStore.runScript(alias, params);
  if (result?.ok) {
    toast.success({
      title: `Ran ${alias}`,
      text: scriptsStore.lastRunResult?.execTime ? `Exec time: ${scriptsStore.lastRunResult.execTime}` : undefined,
    });
    emit("run-success", { alias });
  } 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 } });
}
</script>

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

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

.action-card-item.card-cautious :deep(.action-card) {
  border-color: #e0af68;
}

.action-card-item.card-dangerous :deep(.action-card) {
  border-color: #f7768e;
}
</style>