--- phase: 03-container-actions plan: 03 type: execute wave: 3 depends_on: ["03-02"] files_modified: [n8n-workflow.json] autonomous: true must_haves: truths: - "Multiple container matches show confirmation with inline buttons" - "Confirmation shows list of matching containers" - "User can confirm batch action with single button click" - "Batch actions execute all matching containers in sequence" artifacts: - path: "n8n-workflow.json" provides: "Batch confirmation flow with inline buttons" contains: "Multiple Matches" key_links: - from: "Multiple Matches branch" to: "HTTP Request for keyboard" via: "Build confirmation keyboard" pattern: "inline_keyboard.*Yes.*containers" - from: "Callback handler" to: "Batch execution loop" via: "Execute action for each container" pattern: "for.*containers" --- Implement batch confirmation flow for actions matching multiple containers. Purpose: When a user's query matches multiple containers (e.g., "stop arr" matches sonarr, radarr, lidarr), show a confirmation with the list and an inline button. Per CONTEXT.md, batch actions require confirmation before execution. Output: Extended n8n workflow with batch confirmation and sequential execution. @/home/luc/.claude/get-shit-done/workflows/execute-plan.md @/home/luc/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-container-actions/03-CONTEXT.md @.planning/phases/03-container-actions/03-RESEARCH.md @.planning/phases/03-container-actions/03-02-SUMMARY.md @n8n-workflow.json Task 1: Build batch confirmation message with inline keyboard n8n-workflow.json Replace the placeholder "Multiple Matches" branch from Plan 03-01: 1. **Build Batch Keyboard** (Code node): ```javascript const matches = $json.matches; const action = $json.action; const chatId = $json.chatId; const query = $json.containerQuery; // List matched container names const names = matches.map(m => m.Names[0].replace(/^\//, '')); const shortIds = matches.map(m => m.Id.substring(0, 12)); // Build callback_data - must be ≤64 bytes // For batch: a=action code, c=array of short IDs, t=timestamp const actionCode = action === 'start' ? 's' : action === 'stop' ? 't' : 'r'; const timestamp = Date.now(); // Check size - if too many containers, callback_data might exceed 64 bytes // Each short ID is 12 chars, plus overhead. Max ~3-4 containers safely let callbackData; if (shortIds.length <= 4) { callbackData = JSON.stringify({ a: actionCode, c: shortIds, t: timestamp }); } else { // Too many containers - use abbreviated approach or split // For now, limit to first 4 and note limitation callbackData = JSON.stringify({ a: actionCode, c: shortIds.slice(0, 4), t: timestamp }); } // Format container list const listText = names.map((n, i) => ` • ${n}`).join('\n'); return { json: { chat_id: chatId, text: `Found ${matches.length} containers matching '${query}':\n\n${listText}\n\n${action.charAt(0).toUpperCase() + action.slice(1)} all?`, parse_mode: "HTML", reply_markup: { inline_keyboard: [ [ { text: `Yes, ${action} ${matches.length} containers`, callback_data: callbackData }, { text: "Cancel", callback_data: '{"a":"x"}' } ] ] }, // Store full data for potential later use _meta: { action, containers: matches, timestamp } } }; ``` 2. **Send Batch Confirmation** (HTTP Request node): - Method: POST - URL: `https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage` - Body Content Type: JSON - Body: `{{ JSON.stringify({ chat_id: $json.chat_id, text: $json.text, parse_mode: $json.parse_mode, reply_markup: $json.reply_markup }) }}` 1. "stop arr" when sonarr, radarr, lidarr exist → shows confirmation with list 2. Verify button text shows "Yes, stop 3 containers" 3. Both buttons are visible and clickable Multiple matches show batch confirmation message with inline buttons Task 2: Handle batch confirmation callback n8n-workflow.json Extend the callback handler from Plan 03-02 to handle batch confirmations: 1. **Update Parse Callback Data** (modify existing Code node): Add detection for batch vs single suggestion: ```javascript // Existing code from Plan 03-02... // Detect batch (c is array vs single string) const isBatch = Array.isArray(data.c); const containerIds = isBatch ? data.c : [data.c].filter(Boolean); return { json: { queryId, chatId, messageId, action, containerIds, // Array for batch support containerId: containerIds[0], // For single-container compat expired: isExpired, isBatch, isCancel: action === 'cancel' } }; ``` 2. **Route for Batch** (update Switch node): Add rule before single execution: - Rule: `{{ $json.isBatch }}` equals true AND not cancel AND not expired → Batch Execute branch 3. **Batch Execute** (Code node that builds commands): ```javascript const containerIds = $json.containerIds; const action = $json.action; const timeout = (action === 'stop' || action === 'restart') ? '?t=10' : ''; // Build array of commands const commands = containerIds.map(id => ({ cmd: `curl -s -o /dev/null -w "%{http_code}" --unix-socket /var/run/docker.sock -X POST 'http://localhost/v1.47/containers/${id}/${action}${timeout}'`, containerId: id })); return { json: { commands, action, queryId: $json.queryId, chatId: $json.chatId, messageId: $json.messageId, totalCount: containerIds.length } }; ``` 4. **Execute Batch Loop** (use n8n's SplitInBatches or loop approach): Option A - Sequential Execute (simpler): ```javascript // In n8n, use a Code node that executes sequentially const { execSync } = require('child_process'); const commands = $json.commands; const results = []; for (const { cmd, containerId } of commands) { try { const output = execSync(cmd, { encoding: 'utf8' }).trim(); const statusCode = parseInt(output); results.push({ containerId, success: statusCode === 204 || statusCode === 304, statusCode }); } catch (err) { results.push({ containerId, success: false, error: err.message }); } } const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; return { json: { results, successCount, failCount, totalCount: results.length, action: $json.action, queryId: $json.queryId, chatId: $json.chatId, messageId: $json.messageId } }; ``` NOTE: Using execSync in n8n Code node requires allowedModules in n8n settings. If not available, use multiple Execute Command nodes with SplitInBatches node. 5. **Format Batch Result** (Code node): ```javascript const { successCount, failCount, totalCount, action } = $json; const verb = action === 'start' ? 'started' : action === 'stop' ? 'stopped' : 'restarted'; let message; if (failCount === 0) { message = `Successfully ${verb} ${successCount} container${successCount > 1 ? 's' : ''}`; } else if (successCount === 0) { message = `Failed to ${action} all ${totalCount} containers`; } else { message = `${verb.charAt(0).toUpperCase() + verb.slice(1)} ${successCount}/${totalCount} containers (${failCount} failed)`; } return { json: { message, chatId: $json.chatId, queryId: $json.queryId, messageId: $json.messageId } }; ``` 1. "stop arr" → confirm → all matching containers stop 2. Verify success message shows correct count 3. If one container fails, message shows partial success Batch confirmation executes actions on all matching containers Task 3: Clean up UI after batch action n8n-workflow.json After batch execution, clean up the Telegram UI: 1. **Answer Callback Query** (Telegram node or HTTP Request): - Query ID: `{{ $json.queryId }}` - Text: (empty or brief toast) - Show Alert: false 2. **Delete Confirmation Message** (HTTP Request node): - POST to `https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage` - Body: `{ "chat_id": {{ $json.chatId }}, "message_id": {{ $json.messageId }} }` 3. **Send Result Message** (Telegram Send Message): - Chat ID: `{{ $json.chatId }}` - Text: `{{ $json.message }}` - Parse Mode: HTML Ensure the flow is: 1. User clicks confirm → callback query answered (removes loading state) 2. Confirmation message deleted 3. Result message sent This keeps the chat clean - only the result remains, not the intermediate confirmation. 1. Click confirm → confirmation message disappears 2. Result message appears with count 3. No duplicate messages 4. Click cancel → confirmation message disappears, no result message UI cleaned up after batch action, only result message remains End-to-end batch confirmation verification: 1. "stop arr" (matches 3 containers) → confirmation with list → click confirm → all stop, "Successfully stopped 3 containers" 2. "restart arr" → confirmation → click confirm → all restart 3. "stop arr" → confirmation → click cancel → confirmation deleted, no action 4. "stop arr" → wait 2+ minutes → click → "expired" 5. Single container match (e.g., "stop plex") → still works (no confirmation, direct execution) 6. Suggestion flow (e.g., "stop plx") → still works (single suggestion button) Import updated workflow and test all scenarios. - Multiple matches show batch confirmation with container list - Confirm button executes all containers in sequence - Cancel button dismisses without action - Expired confirmations handled gracefully - Success message shows accurate count - Partial failures reported correctly - UI cleaned up after action (confirmation deleted) After completion, create `.planning/phases/03-container-actions/03-03-SUMMARY.md`