From 1b34c5cabbe4088e9b17ce7b2a42e6763ee14a70 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 5 Oct 2024 20:10:22 +0000 Subject: [PATCH 1/3] feat: add button builder and pull tailscale code out --- command-handler/src/commands/vm.js | 80 ++++--------------- command-handler/src/util/aws/aws-server.js | 24 ++---- command-handler/src/util/button-builder.js | 29 +++++++ .../src/util/hetzner/hetzner-servers.js | 21 +---- .../util/{ => tailscale}/get-devices-info.js | 4 +- .../src/util/tailscale/tailscale.js | 26 ++++++ 6 files changed, 83 insertions(+), 101 deletions(-) create mode 100644 command-handler/src/util/button-builder.js rename command-handler/src/util/{ => tailscale}/get-devices-info.js (91%) create mode 100644 command-handler/src/util/tailscale/tailscale.js diff --git a/command-handler/src/commands/vm.js b/command-handler/src/commands/vm.js index 824d2b4..f383594 100644 --- a/command-handler/src/commands/vm.js +++ b/command-handler/src/commands/vm.js @@ -1,5 +1,6 @@ import hetzner from '../util/hetzner/hetzner-servers.js'; import aws from '../util/aws/aws-server.js'; +import buttonBuilder from '../util/button-builder.js'; export default { description: 'Sets up vm options', @@ -12,40 +13,15 @@ export default { break; } case 'button_create_vm': { + const buttonsArray = [ + { text: "aws", actionId: "button_create_vm_aws" }, + { text: "hetzner", actionId: "button_create_vm_hetzner" }, + ]; + const buttons = buttonBuilder({ buttonsArray, headerText: "choose your platform", fallbackText: "device unsupported" }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - blocks: [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Select a platform to create the vm in:" - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "aws" - }, - "action_id": "button_create_vm_aws" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "hetzner" - }, - "action_id": "button_create_vm_hetzner" - } - ] - } - ], - text: "VM options" + ...buttons }); break; } @@ -142,40 +118,18 @@ export default { }, run: async ({ event, app }) => { + const buttonsArray = [ + { text: "List Servers", actionId: "button_list_servers" }, + { text: "Create Server", actionId: "button_create_vm" }, + ]; + const buttons = buttonBuilder({ buttonsArray, + headerText: "Click one of the buttons below for VM options:", + fallbackText: "device unsupported to use vm command" + }); app.client.chat.postEphemeral({ channel: `${event.channel}`, user: `${event.user}`, - blocks: [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Click one of the buttons below for VM options:" - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "List Servers" - }, - "action_id": "button_list_servers" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Create Server" - }, - "action_id": "button_create_vm" - } - ] - } - ], - text: "VM options" - }) + ...buttons + }); } } \ No newline at end of file diff --git a/command-handler/src/util/aws/aws-server.js b/command-handler/src/util/aws/aws-server.js index 65276fc..52f2d43 100644 --- a/command-handler/src/util/aws/aws-server.js +++ b/command-handler/src/util/aws/aws-server.js @@ -5,10 +5,10 @@ import { TerminateInstancesCommand, } from "@aws-sdk/client-ec2"; import logger from '../logger.js'; -import axios from 'axios'; import 'dotenv/config'; +import tailscale from "../tailscale/tailscale.js"; import formatUser from '../format-user.js'; -import getDevices from '../get-devices-info.js'; +import getDevices from '../tailscale/get-devices-info.js'; import getInstance from "./get-instances.js"; import getSecurityGroups from "./get-security-groups.js"; import getSubnets from "./get-subnets.js"; @@ -132,18 +132,8 @@ export default { try { //get servers and info from tailscale const { deviceId } = await getDevices(serverName); - - await axios.post(`https://api.tailscale.com/api/v2/device/${deviceId}/tags`, - { - "tags": [ - `tag:${userEmail}` - ] - }, { - headers: { - 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}`, - 'Content-Type': 'application/json' - } - }); + await tailscale.setTags({ userEmail, deviceId }); + break; } catch (error) { log.info(`Attempt ${attempts} Failed. Backing off for 30 seconds`) @@ -180,11 +170,7 @@ export default { const { deviceId } = await getDevices(serverName) //delete the device from tailscale - await axios.delete(`https://api.tailscale.com/api/v2/device/${deviceId}`, { - headers: { - 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}` - } - }) + await tailscale.removeDevice({ deviceId }) .catch(error => { log.error('Failed to delete device in tailscale', axiosError(error)); diff --git a/command-handler/src/util/button-builder.js b/command-handler/src/util/button-builder.js new file mode 100644 index 0000000..a5ba131 --- /dev/null +++ b/command-handler/src/util/button-builder.js @@ -0,0 +1,29 @@ +export default function buttonBuilder({ buttonsArray, headerText, fallbackText }) { + let buttonBlocks = buttonsArray.map(button => { + return { + "type": "button", + "text": { + "type": "plain_text", + "text": button.text + }, + "action_id": button.actionId + }; + }); + + return { + blocks: [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": headerText + } + }, + { + "type": "actions", + "elements": buttonBlocks + } + ], + text: fallbackText + }; +} \ No newline at end of file diff --git a/command-handler/src/util/hetzner/hetzner-servers.js b/command-handler/src/util/hetzner/hetzner-servers.js index 6d15c05..cf75b1c 100644 --- a/command-handler/src/util/hetzner/hetzner-servers.js +++ b/command-handler/src/util/hetzner/hetzner-servers.js @@ -2,8 +2,9 @@ import logger from '../logger.js'; import axios from 'axios'; import 'dotenv/config'; import formatUser from '../format-user.js'; -import getDevices from '../get-devices-info.js'; +import getDevices from '../tailscale/get-devices-info.js'; import getServer from './get-servers.js'; +import tailscale from '../tailscale/tailscale.js'; import configUserData from '../get-user-data.js'; import axiosError from '../axios-error-handler.js'; import getHetznerImages from './get-hetzner-images.js'; @@ -126,17 +127,7 @@ export default { //get servers and info from tailscale const { deviceId } = await getDevices(serverName); - await axios.post(`https://api.tailscale.com/api/v2/device/${deviceId}/tags`, - { - "tags": [ - `tag:${userEmail}` - ] - }, { - headers: { - 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}`, - 'Content-Type': 'application/json' - } - }); + await tailscale.setTags({ userEmail, deviceId }); break; } catch (error) { log.info(`Attempt ${attempts} Failed. Backing off for 30 seconds`) @@ -186,11 +177,7 @@ export default { } //delete the device from tailscale - await axios.delete(`https://api.tailscale.com/api/v2/device/${deviceId}`, { - headers: { - 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}` - } - }) + await tailscale.removeDevice({ deviceId }) .catch(error => { log.error('Failed to delete device in tailscale', axiosError(error)); diff --git a/command-handler/src/util/get-devices-info.js b/command-handler/src/util/tailscale/get-devices-info.js similarity index 91% rename from command-handler/src/util/get-devices-info.js rename to command-handler/src/util/tailscale/get-devices-info.js index 93b2d8f..699b790 100644 --- a/command-handler/src/util/get-devices-info.js +++ b/command-handler/src/util/tailscale/get-devices-info.js @@ -1,7 +1,7 @@ import 'dotenv/config'; import axios from 'axios'; -import logger from './logger.js'; -import axiosError from './axios-error-handler.js'; +import logger from '../logger.js'; +import axiosError from '../axios-error-handler.js'; const log = logger(); diff --git a/command-handler/src/util/tailscale/tailscale.js b/command-handler/src/util/tailscale/tailscale.js new file mode 100644 index 0000000..73dd69f --- /dev/null +++ b/command-handler/src/util/tailscale/tailscale.js @@ -0,0 +1,26 @@ +import 'dotenv/config' +import axios from 'axios' + +export default { + setTags: async ({ userEmail, deviceId }) => { + await axios.post(`https://api.tailscale.com/api/v2/device/${deviceId}/tags`, + { + "tags": [ + `tag:${userEmail}` + ] + }, { + headers: { + 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}`, + 'Content-Type': 'application/json' + } + }); + }, + + removeDevice: async ({ deviceId }) => { + await axios.delete(`https://api.tailscale.com/api/v2/device/${deviceId}`, { + headers: { + 'Authorization': `Bearer ${process.env.TAILSCALE_API_TOKEN}` + } + }); + } +} \ No newline at end of file From 33f05cec60219d7fbd95731be1837ad1e3c73f74 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 5 Oct 2024 22:30:28 +0000 Subject: [PATCH 2/3] chore: reformat slackbot outbot and use buttonBuilder --- command-handler/src/commands/vm.js | 35 +++- command-handler/src/util/aws/aws-server.js | 164 ++++-------------- command-handler/src/util/button-builder.js | 3 +- .../src/util/hetzner/hetzner-servers.js | 150 ++++------------ 4 files changed, 104 insertions(+), 248 deletions(-) diff --git a/command-handler/src/commands/vm.js b/command-handler/src/commands/vm.js index f383594..23a2bfc 100644 --- a/command-handler/src/commands/vm.js +++ b/command-handler/src/commands/vm.js @@ -8,8 +8,39 @@ export default { button: async ({ app, actionId, body, response }) => { switch(actionId) { case 'button_list_servers': { - await aws.listServers({ app, body }); - await hetzner.listServers({ app, body }); + const servers = []; + const buttons = []; + const buttonsArray = [ + { text: "List Servers", actionId: "button_list_servers" }, + { text: "Create Server", actionId: "button_create_vm" }, + ]; + + servers.push(await aws.listServers({ app, body })); + servers.push(await hetzner.listServers({ app, body })); + for (const server in servers) { + buttonsArray.push({ text: "Start", actionId: `button_start_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region })}, + { text: "Stop", actionId: `button_stop_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region })}, + { text: "Delete", actionId: `button_delete_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, serverName: server.serverName, region: server.region })} + ); + buttons.push(buttonBuilder({ buttonsArray, + headerText: `Server: ${server.serverName}\nServer id: ${server.serverID}\nRegion: ${server.region}\nStatus: ${server.status}\nConnect: ${server.connect}`, + fallbackText: "device not supported to use vm command" + })); + } + + if (servers.length) { + app.client.chat.postEphemeral({ + channel: `${body.channel}`, + user: `${body.user}`, + ...buttons + }); + } else { + app.client.chat.postEphemeral({ + channel: `${body.channel}`, + user: `${body.user}`, + test: "You don't currently have any servers" + }); + } break; } case 'button_create_vm': { diff --git a/command-handler/src/util/aws/aws-server.js b/command-handler/src/util/aws/aws-server.js index 52f2d43..78dfb3b 100644 --- a/command-handler/src/util/aws/aws-server.js +++ b/command-handler/src/util/aws/aws-server.js @@ -11,6 +11,7 @@ import formatUser from '../format-user.js'; import getDevices from '../tailscale/get-devices-info.js'; import getInstance from "./get-instances.js"; import getSecurityGroups from "./get-security-groups.js"; +import buttonBuilder from "../button-builder.js"; import getSubnets from "./get-subnets.js"; import configUserData from "../get-user-data.js"; import getAwsImages from "./get-aws-images.js"; @@ -218,12 +219,8 @@ export default { //get all the regions const regions = process.env.AWS_REGIONS.split(',').map(region => region.trim()).filter(region => region); //get the instances from aws - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Servers in AWS:` - }); for (const region of regions) { + const servers = []; const instances = await getInstance({ userEmail, region }); // list the servers and build the buttons for (const instance of instances) { @@ -231,54 +228,17 @@ export default { const serverName = instance.Tags.find(tag => tag.Key === 'Name')?.Value; const { deviceIP } = await getDevices(serverName); - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - blocks: [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": `Server: ${serverName}\nServer id: ${instance.InstanceId}\nRegion: ${region}\nStatus: ${instance.State.Name}\nConnect: https://login.tailscale.com/admin/machines/${deviceIP}` - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Start" - }, - "action_id": `button_start_aws`, - "value": JSON.stringify({instanceId: instance.InstanceId, region}) - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Stop" - }, - "action_id": `button_stop_aws`, - "value": JSON.stringify({instanceId: instance.InstanceId, region}) - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Delete" - }, - "action_id": `button_delete_aws`, - "value": JSON.stringify({instanceId: instance.InstanceId, serverName, region}) - } - ] - } - ], - text: "VM options" + servers.push({ + cloud: "aws", + serverName: `${serverName}`, + serverID: `${instance.InstanceId}`, + region: `${region}`, + status: `${instance.State.Name}`, + connect: `https://login.tailscale.com/admin/machines/${deviceIP}` }); } } + return servers; // example id: to-describe-an-amazon-ec2-instance-1529025982172 }, @@ -320,6 +280,7 @@ export default { const { region } = data; //get the aws images const images = await getAwsImages({ region }); + const buttonsArray = []; //return if it fails to get the images. if (!images) { @@ -333,39 +294,22 @@ export default { } //build button for user to select - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select an image:` - }); for (const image of images) { data.imageName = image.Name; data.ami = image.ImageId; - app.client.chat.postEphemeral({ + buttonsArray.push({ text: image.Name, actionId: 'button_create_image_aws', value: JSON.stringify(data) }) + } + + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an image', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${image.Name}` - }, - "action_id": `button_create_image_aws`, - "value": JSON.stringify(data) - }, - ] - } - ], - text: "Select an image:" - }); - } + ...buttons + }); }, selectRegion: async ({app, body }) => { + const buttonsArray = []; //get the regions from the env variable const regions = process.env.AWS_REGIONS.split(',').map(region => region.trim()).filter(region => region); @@ -381,68 +325,30 @@ export default { } //build button for user to select - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select a region:` - }); for (const region of regions) { - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${region}` - }, - "action_id": `button_select_aws_server`, - "value": JSON.stringify({region}) - }, - ] - } - ], - text: "Select a region:" - }) + buttonsArray.push({ text: region, actionId: 'button_select_aws_server', value: JSON.stringify({ region }) }); } + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a region', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ + channel: `${body.channel.id}`, + user: `${body.user.id}`, + ...buttons + }) }, selectServer: async ({app, body, data }) => { const instances = process.env.AWS_INSTANCES.split(',').map(instance => instance.trim()).filter(instance => instance); - - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select an instance:` - }); + const buttonsArray = []; for (const instance of instances) { - data.instanceType = instance; - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${instance}` - }, - "action_id": `button_select_aws_image`, - "value": JSON.stringify(data) - }, - ] - } - ], - text: "Select an instance" - }) + data.instanceType = instance; + buttonsArray.push({ text: instance, actionId: 'button_select_aws_image', value: JSON.stringify({ data }) }); }; + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an instance', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ + channel: `${body.channel.id}`, + user: `${body.user.id}`, + ...buttons + }); } }; \ No newline at end of file diff --git a/command-handler/src/util/button-builder.js b/command-handler/src/util/button-builder.js index a5ba131..e3ee8d7 100644 --- a/command-handler/src/util/button-builder.js +++ b/command-handler/src/util/button-builder.js @@ -6,7 +6,8 @@ export default function buttonBuilder({ buttonsArray, headerText, fallbackText } "type": "plain_text", "text": button.text }, - "action_id": button.actionId + "action_id": button.actionId, + "value": button.value || "" }; }); diff --git a/command-handler/src/util/hetzner/hetzner-servers.js b/command-handler/src/util/hetzner/hetzner-servers.js index cf75b1c..b55d9a2 100644 --- a/command-handler/src/util/hetzner/hetzner-servers.js +++ b/command-handler/src/util/hetzner/hetzner-servers.js @@ -215,6 +215,7 @@ export default { //list the servers listServers: async ({ app, body }) => { const data = await getServer(); + const servers = []; // Call the users.info method using the WebClient const info = await app.client.users.info({ @@ -236,54 +237,19 @@ export default { for (const server of data.data.servers) { if (server.labels.owner === userEmail) { const { deviceIP } = await getDevices(server.name); - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - blocks: [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": `Server: ${server.name}\nServer id: ${server.id}\nStatus: ${server.status}\nConnect: https://login.tailscale.com/admin/machines/${deviceIP}` - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Start" - }, - "action_id": `button_start_hetzner`, - "value": JSON.stringify({vmID: server.id}) - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Stop" - }, - "action_id": `button_stop_hetzner`, - "value": JSON.stringify({vmID: server.id}) - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Delete" - }, - "action_id": `button_delete_hetzner`, - "value": JSON.stringify({serverName: server.name}) - } - ] - } - ], - text: "VM options" - }) + + servers.push({ + cloud: "hetzner", + serverName: `${server.name}`, + serverID: `${server.id}`, + region: `${region}`, + status: `${server.status}`, + connect: `https://login.tailscale.com/admin/machines/${deviceIP}` + }); } } + + return servers; }, //start a hetzner server @@ -341,6 +307,7 @@ export default { //code to build button UI selectImage: async ({app, body, data }) => { + const buttonsArray = []; //get the hetzner images const images = await getHetznerImages(); @@ -356,39 +323,22 @@ export default { } //build button for user to select - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select an image:` - }); for (const image of images) { data.imageName = image.description; data.imageID = image.id; - app.client.chat.postEphemeral({ + buttonsArray.push({ text: image.description, actionId: 'button_create_image_hetzner', value: JSON.stringify(data) }) + } + + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an image', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${image.description}` - }, - "action_id": `button_create_image_hetzner`, - "value": JSON.stringify(data) - }, - ] - } - ], - text: "Select an image:" - }) - } + ...buttons + }); }, selectRegion: async ({app, body }) => { + const buttonsArray = []; //get the regions from the env variable const regions = process.env.HETZNER_REGIONS.split(',').map(region => region.trim()).filter(region => region); @@ -404,37 +354,19 @@ export default { } //build button for user to select - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select a region:` - }); for (const region of regions) { - app.client.chat.postEphemeral({ + buttonsArray.push({ text: region, actionId: 'button_select_hetzner_server', value: JSON.stringify({ region }) }); + } + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a region', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${region}` - }, - "action_id": `button_select_hetzner_server`, - "value": JSON.stringify({region}) - }, - ] - } - ], - text: "Select a region:" - }) - } + ...buttons + }); }, selectServer: async ({app, body, data }) => { + const buttonsArray = []; const serverTypes = process.env.HETZNER_SERVER_TYPES.split(',').map(server => server.trim()).filter(server => server); app.client.chat.postEphemeral({ @@ -445,27 +377,13 @@ export default { for (const serverType of serverTypes) { data.serverType = serverType; - app.client.chat.postEphemeral({ + buttonsArray.push({ text: serverType, actionId: 'button_select_hetzner_image', value: JSON.stringify({ data }) }); + }; + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a server', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - blocks: [ - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": `${serverType}` - }, - "action_id": `button_select_hetzner_image`, - "value": JSON.stringify(data) - }, - ] - } - ], - text: "Select a server" - }) - }; + ...buttons + }); } } \ No newline at end of file From ed88e67952e4cd1e44a36d50f5cd96705d99bd52 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 6 Oct 2024 04:13:46 +0000 Subject: [PATCH 3/3] fix: buttonBuilder and slackbot output --- command-handler/src/commands/vm.js | 140 ++++++++---------- command-handler/src/util/aws/aws-server.js | 15 +- command-handler/src/util/button-builder.js | 2 +- .../src/util/hetzner/hetzner-servers.js | 26 ++-- 4 files changed, 78 insertions(+), 105 deletions(-) diff --git a/command-handler/src/commands/vm.js b/command-handler/src/commands/vm.js index 23a2bfc..437385a 100644 --- a/command-handler/src/commands/vm.js +++ b/command-handler/src/commands/vm.js @@ -6,44 +6,49 @@ export default { description: 'Sets up vm options', button: async ({ app, actionId, body, response }) => { - switch(actionId) { - case 'button_list_servers': { - const servers = []; - const buttons = []; - const buttonsArray = [ - { text: "List Servers", actionId: "button_list_servers" }, - { text: "Create Server", actionId: "button_create_vm" }, - ]; - - servers.push(await aws.listServers({ app, body })); - servers.push(await hetzner.listServers({ app, body })); - for (const server in servers) { - buttonsArray.push({ text: "Start", actionId: `button_start_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region })}, - { text: "Stop", actionId: `button_stop_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region })}, - { text: "Delete", actionId: `button_delete_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, serverName: server.serverName, region: server.region })} - ); - buttons.push(buttonBuilder({ buttonsArray, - headerText: `Server: ${server.serverName}\nServer id: ${server.serverID}\nRegion: ${server.region}\nStatus: ${server.status}\nConnect: ${server.connect}`, - fallbackText: "device not supported to use vm command" - })); + if (actionId === 'button_list_servers') { + const servers = []; + const blocks = []; // Use this to accumulate all blocks + + // Fetch servers from AWS and Hetzner + servers.push(...await aws.listServers({ app, body })); + servers.push(...await hetzner.listServers({ app, body })); + + // Check if there are any servers + if (servers.length) { + for (const server of servers) { + const buttonsArray = [ + { text: "Start", actionId: `button_start_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region }) }, + { text: "Stop", actionId: `button_stop_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, vmID: server.serverID, region: server.region }) }, + { text: "Delete", actionId: `button_delete_${server.cloud}`, value: JSON.stringify({ instanceId: server.serverID, serverName: server.serverName, region: server.region }) } + ]; + + // Build buttons and add them to blocks + const buttonBlock = buttonBuilder({ + buttonsArray, + headerText: `Cloud: ${server.cloud}\nServer: ${server.serverName}\nServer ID: ${server.serverID}\nRegion: ${server.region}\nStatus: ${server.status}\nConnect: ${server.connect}`, + fallbackText: "Device not supported to use VM command" + }); + + // Push the blocks into the main blocks array + blocks.push(...buttonBlock.blocks); } - - if (servers.length) { - app.client.chat.postEphemeral({ - channel: `${body.channel}`, - user: `${body.user}`, - ...buttons - }); - } else { - app.client.chat.postEphemeral({ - channel: `${body.channel}`, - user: `${body.user}`, - test: "You don't currently have any servers" + + // Send the combined blocks in a single message + await app.client.chat.postEphemeral({ + channel: `${body.channel.id}`, + user: `${body.user.id}`, + text: 'Server List', + blocks, // Combine all button blocks + }); + } else { + await app.client.chat.postEphemeral({ + channel: `${body.channel.id}`, + user: `${body.user.id}`, + text: "You don't currently have any servers" }); - } - break; } - case 'button_create_vm': { + } else if (actionId === 'button_create_vm') { const buttonsArray = [ { text: "aws", actionId: "button_create_vm_aws" }, { text: "hetzner", actionId: "button_create_vm_hetzner" }, @@ -52,100 +57,70 @@ export default { app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select a platform', ...buttons }); - break; - } - case 'button_start_aws': { + } else if (actionId === 'button_start_aws') { const { instanceId, region } = JSON.parse(body.actions[0].value); - + aws.startServer({ app, body, instanceId, region }); - break; - } - case 'button_stop_aws': { + } else if (actionId === 'button_stop_aws') { const { instanceId, region } = JSON.parse(body.actions[0].value); aws.stopServer({ app, body, instanceId, region }); - break; - } - case 'button_delete_aws': { + } else if (actionId === 'button_delete_aws') { const { instanceId, serverName, region } = JSON.parse(body.actions[0].value); //delete the server aws.deleteServer({ app, body, instanceId, serverName, region }); - break; - } - case 'button_start_hetzner': { + } else if (actionId === 'button_start_hetzner') { const { vmID } = JSON.parse(body.actions[0].value); //start a hetzner server hetzner.startServer({ app, body, vmID }); - break; - } - case 'button_stop_hetzner': { + } else if (actionId === 'button_stop_hetzner') { const { vmID } = JSON.parse(body.actions[0].value); //stop a hetzner server hetzner.stopServer({ app, body, vmID }); - break; - } - case 'button_delete_hetzner': { + } else if (actionId === 'button_delete_hetzner') { const { serverName } = JSON.parse(body.actions[0].value); //delete the server hetzner.deleteServer({ app, body, serverName }); - break; - } - case 'button_create_image_aws': { + } else if (actionId.startsWith('button_create_image_aws')) { const { imageName, ami, region, instanceType } = JSON.parse(body.actions[0].value); aws.createServer({ app, body, imageName, ami, region, instanceType }); - break; - } - case 'button_create_image_hetzner': { + } else if (actionId.startsWith('button_create_image_hetzner')) { const { imageID, imageName, region, serverType } = JSON.parse(body.actions[0].value); hetzner.createServer({ app, body, imageID, imageName, region, serverType }); - break; - } - case 'button_create_vm_hetzner': { + } else if (actionId === 'button_create_vm_hetzner') { //select the hetzner server to create before calling the create server hetzner.selectRegion({ app, body }); - break; - } - case 'button_create_vm_aws': { + } else if (actionId === 'button_create_vm_aws') { //select the aws server to create before calling the create server aws.selectRegion({ app, body }); - break; - } - case 'button_select_hetzner_server': { + } else if (actionId.startsWith('button_select_hetzner_server')) { const data = JSON.parse(body.actions[0].value); //select the hetzner server to create before calling the create server hetzner.selectServer({ app, body, data }); - break; - } - case 'button_select_aws_server': { + } else if (actionId.startsWith('button_select_aws_server')) { const data = JSON.parse(body.actions[0].value); //select the asw server to create before calling the create server aws.selectServer({ app, body, data }); - break; - } - case 'button_select_hetzner_image': { + } else if (actionId.startsWith('button_select_hetzner_image')) { const data = JSON.parse(body.actions[0].value); //select the hetzner server to create before calling the create server hetzner.selectImage({ app, body, data }); - break; - } - case 'button_select_aws_image': { + } else if (actionId.startsWith('button_select_aws_image')) { const data = JSON.parse(body.actions[0].value); //select the asw server to create before calling the create server aws.selectImage({ app, body, data }); - break; - } - default: { + } else { response({ text: `This button is registered with the vm command, but does not have an action associated with it.` }); } - } }, run: async ({ event, app }) => { @@ -160,6 +135,7 @@ export default { app.client.chat.postEphemeral({ channel: `${event.channel}`, user: `${event.user}`, + text: 'Create a vm', ...buttons }); } diff --git a/command-handler/src/util/aws/aws-server.js b/command-handler/src/util/aws/aws-server.js index 78dfb3b..af03e7c 100644 --- a/command-handler/src/util/aws/aws-server.js +++ b/command-handler/src/util/aws/aws-server.js @@ -162,7 +162,7 @@ export default { app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - text: `The server has been created: https://login.tailscale.com/admin/machines/${deviceIP}` + text: `Cloud: aws\nServer: ${serverName}\nRegion: ${region}\nStatus: Created\nConnect: https://login.tailscale.com/admin/machines/${deviceIP}` }); }, @@ -207,6 +207,7 @@ export default { listServers: async({ app, body }) => { // Call the users.info method using the WebClient + const servers = []; const info = await app.client.users.info({ user: body.user.id }) @@ -220,7 +221,6 @@ export default { const regions = process.env.AWS_REGIONS.split(',').map(region => region.trim()).filter(region => region); //get the instances from aws for (const region of regions) { - const servers = []; const instances = await getInstance({ userEmail, region }); // list the servers and build the buttons for (const instance of instances) { @@ -297,13 +297,14 @@ export default { for (const image of images) { data.imageName = image.Name; data.ami = image.ImageId; - buttonsArray.push({ text: image.Name, actionId: 'button_create_image_aws', value: JSON.stringify(data) }) + buttonsArray.push({ text: image.Name, actionId: `button_create_image_aws_${image.Name}`, value: JSON.stringify(data) }) } const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an image', fallbackText: 'unsupported device' }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select an image', ...buttons }); }, @@ -326,12 +327,15 @@ export default { //build button for user to select for (const region of regions) { - buttonsArray.push({ text: region, actionId: 'button_select_aws_server', value: JSON.stringify({ region }) }); + buttonsArray.push({ text: region, actionId: `button_select_aws_server_${region}`, value: JSON.stringify({ region }) }); } + const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a region', fallbackText: 'unsupported device' }); + app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select a region', ...buttons }) }, @@ -342,12 +346,13 @@ export default { for (const instance of instances) { data.instanceType = instance; - buttonsArray.push({ text: instance, actionId: 'button_select_aws_image', value: JSON.stringify({ data }) }); + buttonsArray.push({ text: instance, actionId: `button_select_aws_image_${instance}`, value: JSON.stringify(data) }); }; const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an instance', fallbackText: 'unsupported device' }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select an instance', ...buttons }); } diff --git a/command-handler/src/util/button-builder.js b/command-handler/src/util/button-builder.js index e3ee8d7..267d651 100644 --- a/command-handler/src/util/button-builder.js +++ b/command-handler/src/util/button-builder.js @@ -7,7 +7,7 @@ export default function buttonBuilder({ buttonsArray, headerText, fallbackText } "text": button.text }, "action_id": button.actionId, - "value": button.value || "" + "value": button.value }; }); diff --git a/command-handler/src/util/hetzner/hetzner-servers.js b/command-handler/src/util/hetzner/hetzner-servers.js index b55d9a2..464eed0 100644 --- a/command-handler/src/util/hetzner/hetzner-servers.js +++ b/command-handler/src/util/hetzner/hetzner-servers.js @@ -6,6 +6,7 @@ import getDevices from '../tailscale/get-devices-info.js'; import getServer from './get-servers.js'; import tailscale from '../tailscale/tailscale.js'; import configUserData from '../get-user-data.js'; +import buttonBuilder from '../button-builder.js'; import axiosError from '../axios-error-handler.js'; import getHetznerImages from './get-hetzner-images.js'; import { uniqueNamesGenerator, colors, animals } from 'unique-names-generator'; @@ -155,7 +156,7 @@ export default { app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, - text: `The server has been created: https://login.tailscale.com/admin/machines/${deviceIP}` + text: `Cloud: hetzner\nServer: ${serverName}\nRegion: ${region}\nStatus: Created\nConnect: https://login.tailscale.com/admin/machines/${deviceIP}` }); }, @@ -227,12 +228,6 @@ export default { const userEmail = formatUser(info.user.profile.email); - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Servers in Hetzner:` - }); - //list the servers and build the buttons for (const server of data.data.servers) { if (server.labels.owner === userEmail) { @@ -242,7 +237,7 @@ export default { cloud: "hetzner", serverName: `${server.name}`, serverID: `${server.id}`, - region: `${region}`, + region: `${server.datacenter.location.name}`, status: `${server.status}`, connect: `https://login.tailscale.com/admin/machines/${deviceIP}` }); @@ -326,13 +321,14 @@ export default { for (const image of images) { data.imageName = image.description; data.imageID = image.id; - buttonsArray.push({ text: image.description, actionId: 'button_create_image_hetzner', value: JSON.stringify(data) }) + buttonsArray.push({ text: image.description, actionId: `button_create_image_hetzner${image.description}`, value: JSON.stringify(data) }) } const buttons = buttonBuilder({ buttonsArray, headerText: 'Select an image', fallbackText: 'unsupported device' }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select an image', ...buttons }); }, @@ -355,12 +351,13 @@ export default { //build button for user to select for (const region of regions) { - buttonsArray.push({ text: region, actionId: 'button_select_hetzner_server', value: JSON.stringify({ region }) }); + buttonsArray.push({ text: region, actionId: `button_select_hetzner_server${region}`, value: JSON.stringify({ region }) }); } const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a region', fallbackText: 'unsupported device' }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select a region', ...buttons }); }, @@ -368,21 +365,16 @@ export default { selectServer: async ({app, body, data }) => { const buttonsArray = []; const serverTypes = process.env.HETZNER_SERVER_TYPES.split(',').map(server => server.trim()).filter(server => server); - - app.client.chat.postEphemeral({ - channel: `${body.channel.id}`, - user: `${body.user.id}`, - text: `Select a server:` - }); for (const serverType of serverTypes) { data.serverType = serverType; - buttonsArray.push({ text: serverType, actionId: 'button_select_hetzner_image', value: JSON.stringify({ data }) }); + buttonsArray.push({ text: serverType, actionId: `button_select_hetzner_image_${serverType}`, value: JSON.stringify(data) }); }; const buttons = buttonBuilder({ buttonsArray, headerText: 'Select a server', fallbackText: 'unsupported device' }); app.client.chat.postEphemeral({ channel: `${body.channel.id}`, user: `${body.user.id}`, + text: 'select a server', ...buttons }); }