--- import BaseLayout from "@/layouts/BaseLayout.astro"; import { getDatabase, schema } from "@/db"; import { eq, desc } from "drizzle-orm"; import { Zap, Plus, Trash2, Edit2, Play, Pause, GitPullRequest, Tag, Users, MessageSquare, CheckCircle2, XCircle, Clock, ArrowRight, Settings2, History, } from "lucide-react"; const user = Astro.locals.user; if (!user) { return Astro.redirect("/login"); } const db = getDatabase(); const repoId = Astro.url.searchParams.get("repo") || ""; const view = Astro.url.searchParams.get("view") || "rules"; // Get user's repositories let repositories: { id: string; name: string; fullName?: string }[] = []; try { const repos = await db.query.repositories.findMany({ where: eq(schema.repositories.ownerId, user.id), columns: { id: true, name: true }, with: { owner: { columns: { username: true } } }, }); repositories = repos.map((r) => ({ id: r.id, name: r.name, fullName: `${r.owner.username}/${r.name}`, })); } catch (e) {} // Get automation rules for selected repo interface AutomationRule { id: string; name: string; description?: string; trigger: string; conditions?: string; actions: string; isEnabled: boolean; runCount: number; lastRunAt?: string; } interface AutomationExecution { id: string; ruleName: string; triggerEvent: string; status: string; createdAt: string; durationMs?: number; } let rules: AutomationRule[] = []; let executions: AutomationExecution[] = []; try { if (repoId) { const dbRules = await db.query.automationRules.findMany({ where: eq(schema.automationRules.repositoryId, repoId), orderBy: [desc(schema.automationRules.createdAt)], }); rules = dbRules.map(r => ({ id: r.id, name: r.name, description: r.description || undefined, trigger: r.trigger, conditions: r.conditions || undefined, actions: r.actions, isEnabled: r.isEnabled ?? true, runCount: r.runCount || 0, lastRunAt: r.lastRunAt ? r.lastRunAt.toISOString() : undefined, })); // Get recent executions if (view === "history") { const ruleIds = rules.map(r => r.id); if (ruleIds.length > 0) { const dbExecutions = await db.query.automationExecutions.findMany({ orderBy: [desc(schema.automationExecutions.createdAt)], limit: 50, with: { rule: true, }, }); executions = dbExecutions .filter(e => ruleIds.includes(e.ruleId)) .map(e => ({ id: e.id, ruleName: e.rule?.name || "Unknown", triggerEvent: e.triggerEvent, status: e.status, createdAt: e.createdAt.toISOString(), durationMs: e.durationMs || undefined, })); } } } } catch (e) { console.error("Failed to fetch automation rules:", e); } const triggerLabels: Record = { pr_opened: { label: "PR Opened", icon: GitPullRequest, color: "text-green-400" }, pr_merged: { label: "PR Merged", icon: GitPullRequest, color: "text-purple-400" }, pr_approved: { label: "PR Approved", icon: CheckCircle2, color: "text-green-400" }, ci_passed: { label: "CI Passed", icon: CheckCircle2, color: "text-green-400" }, ci_failed: { label: "CI Failed", icon: XCircle, color: "text-red-400" }, label_added: { label: "Label Added", icon: Tag, color: "text-blue-400" }, review_requested: { label: "Review Requested", icon: Users, color: "text-orange-400" }, comment_added: { label: "Comment Added", icon: MessageSquare, color: "text-cyan-400" }, }; const actionLabels: Record = { add_label: "Add Label", remove_label: "Remove Label", assign_reviewer: "Assign Reviewer", add_to_merge_queue: "Add to Merge Queue", add_comment: "Add Comment", trigger_ai_review: "Trigger AI Review", trigger_iac_hook: "Trigger IaC Hook", notify_slack: "Notify Slack", close_pr: "Close PR", }; function timeAgo(date: string) { const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000); if (seconds < 60) return "just now"; if (seconds < 3600) return Math.floor(seconds / 60) + "m ago"; if (seconds < 86400) return Math.floor(seconds / 3600) + "h ago"; return Math.floor(seconds / 86400) + "d ago"; } function parseActions(actionsJson: string): { type: string; label: string }[] { try { const parsed = JSON.parse(actionsJson); return parsed.map((a: { type: string }) => ({ type: a.type, label: actionLabels[a.type] || a.type, })); } catch { return []; } } ---

Automations

Automate your workflow with custom rules

{repoId && ( )}
{repoId && ( )} {!repoId ? (

Select a Repository

Choose a repository to configure automations

) : view === "rules" ? (
{rules.length === 0 ? (

No Automations Yet

Create rules to automate your workflow

) : (
{rules.map((rule) => { const trigger = triggerLabels[rule.trigger] || { label: rule.trigger, icon: Zap, color: "text-muted-foreground" }; const TriggerIcon = trigger.icon; const actions = parseActions(rule.actions); return (
{/* Trigger Icon */}
{/* Rule Info */}
{rule.name} {!rule.isEnabled && ( Disabled )}
{rule.description && (

{rule.description}

)} {/* Trigger → Actions flow */}
{trigger.label} {actions.slice(0, 3).map((action) => ( {action.label} ))} {actions.length > 3 && ( +{actions.length - 3} more )}
{rule.runCount} runs {rule.lastRunAt && ( Last run {timeAgo(rule.lastRunAt)} )}
{/* Actions */}
); })}
)}
) : (
{executions.length === 0 ? (

No Execution History

Automation runs will appear here

) : (
{executions.map((exec) => (
{exec.status === "success" ? ( ) : exec.status === "failed" ? ( ) : ( )}
{exec.ruleName}
Triggered by: {triggerLabels[exec.triggerEvent]?.label || exec.triggerEvent}
{timeAgo(exec.createdAt)}
{exec.durationMs && (
{exec.durationMs}ms
)}
))}
)}
)} {repoId && view === "rules" && (

Quick Templates

)}