Skip to main content
Glama
ActionCard.vue7.04 kB
<template> <ActionCardLayout :noInteraction="noInteraction || !shouldAllowClick" :selected="selected" :actionFailed="actionFailed" :highlightedFailed="failed" :abbr="actionKindToAbbreviation(action.kind)" :description="action.kind === ActionKind.Manual ? action.description : ''" :componentSchemaName="action.componentSchemaName" :componentName="action.componentName" :componentId="action.componentId" :actor="action.actor" @click="handleClick" > <template #icons> <Icon v-if="actionRunning" :class="clsx(themeClasses('text-action-300', 'text-action-300'))" name="loader" size="sm" class="animate-spin" /> <Icon v-else-if="actionOnHold" :class=" clsx( action.holdStatusInfluencedBy?.length > 0 ? [ 'opacity-30', themeClasses('text-warning-500', 'text-warning-300'), ] : themeClasses('text-warning-400', 'text-warning-300'), ) " name="circle-stop" size="sm" /> <template v-else-if="actionFailed"> <Icon :class="clsx(themeClasses('text-action-700', 'text-action-300'))" name="play" size="sm" @click.stop="retry" /> <Icon :class=" clsx(themeClasses('text-destructive-500', 'text-destructive-600')) " name="x-hex-outline" size="sm" /> </template> <Icon :class="actionIconClass(action.kind)" :name="actionIcon(action.kind)" size="sm" /> </template> <template #interaction> <ConfirmHoldModal v-if="!noInteraction" ref="confirmRef" :ok="finishHold" /> <DropdownMenu v-if="!noInteraction" ref="contextMenuRef" :forceAbove="false" forceAlignRight > <h5 class="text-neutral-400 pl-2xs">ACTIONS:</h5> <!-- View action details --> <DropdownMenuItem icon="eye" label="View details" @select="navigateToActionDetails" /> <!-- Go to component --> <DropdownMenuItem v-if="props.action.componentId" icon="component" label="Go to component" @select="navigateToComponent" /> <!-- Action state controls --> <DropdownMenuItem v-if="action.state === ActionState.Queued" icon="circle-stop" iconClass="text-warning-400" label="Put on hold" @select="hold" /> <DropdownMenuItem v-if="action.state === ActionState.OnHold" icon="nested-arrow-right" iconClass="text-action-400" label="Put in Queue" @select="retry" /> <DropdownMenuItem icon="x" iconClass="text-destructive-500 dark:text-destructive-600" label="Remove from list" @select="remove" /> </DropdownMenu> <DetailsPanelMenuIcon v-if="!noInteraction" :selected="contextMenuRef?.isOpen" @click.stop="(e: MouseEvent) => contextMenuRef?.open(e, false)" /> </template> </ActionCardLayout> </template> <script lang="ts" setup> import { computed, ref } from "vue"; import { useRouter, useRoute } from "vue-router"; import clsx from "clsx"; import { Icon, themeClasses, DropdownMenu, DropdownMenuItem, } from "@si/vue-lib/design-system"; import { ActionKind, ActionState } from "@/api/sdf/dal/action"; import ConfirmHoldModal from "./ConfirmHoldModal.vue"; import DetailsPanelMenuIcon from "./layout_components/DetailsPanelMenuIcon.vue"; import ActionCardLayout from "./ActionCardLayout.vue"; import { actionKindToAbbreviation, actionIconClass, actionIcon, } from "./logic_composables/action"; import { ActionProposedView } from "./types"; import { routes, useApi } from "./api_composables"; const props = defineProps<{ action: ActionProposedView; slim?: boolean; selected?: boolean; failed?: boolean; noInteraction?: boolean; }>(); const emit = defineEmits<{ (e: "click", action: ActionProposedView): void; }>(); const router = useRouter(); const route = useRoute(); const confirmRef = ref<InstanceType<typeof ConfirmHoldModal> | null>(null); const contextMenuRef = ref<InstanceType<typeof DropdownMenu>>(); // Navigate to action details const navigateToActionDetails = () => { router.push({ name: "new-hotness-action", params: { workspacePk: route.params.workspacePk, changeSetId: route.params.changeSetId, actionId: props.action.id, }, }); }; // Navigate to component const navigateToComponent = () => { if (!props.action.componentId) return; router.push({ name: "new-hotness-component", params: { workspacePk: route.params.workspacePk, changeSetId: route.params.changeSetId, componentId: props.action.componentId, }, }); }; // Handle click on the card const handleClick = () => { if (props.noInteraction || !shouldAllowClick.value) { return; } emit("click", props.action); // Navigate to action details which will show the latest function run navigateToActionDetails(); }; const actionOnHold = computed(() => { return ( props.action.state === ActionState.OnHold || (props.action.holdStatusInfluencedBy?.length ?? 0) > 0 ); }); const actionFailed = computed(() => { return props.action.state === ActionState.Failed; }); const actionRunning = computed(() => { return props.action.state === ActionState.Dispatched; }); const shouldAllowClick = computed(() => { // Allow clicking on failed or running actions if (actionFailed.value || actionRunning.value) { return true; } // Allow clicking on on-hold actions that have a funcRunId (indicating previous execution) if (actionOnHold.value && props.action.funcRunId) { return true; } return false; }); // Action handling methods const hold = () => { const hasDependencies = (props.action.myDependencies?.length ?? 0) > 0; if (hasDependencies) { confirmRef.value?.open(); } else { finishHold(); } }; const holdApi = useApi(); const finishHold = async () => { const call = holdApi.endpoint(routes.ActionHold, { id: props.action.id }); // This route can mutate head, so we do not need to handle new change set semantics. await call.put({}); confirmRef.value?.close(); }; const retryApi = useApi(); const retry = async () => { const call = retryApi.endpoint(routes.ActionRetry, { id: props.action.id }); // This route can mutate head, so we do not need to handle new change set semantics. await call.put({}); }; const removeApi = useApi(); const remove = async () => { const call = removeApi.endpoint(routes.ActionCancel, { id: props.action.id }); // This route can mutate head, so we do not need to handle new change set semantics. await call.put({}); }; </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server