diff --git a/backend/agent-manager.ts b/backend/agent-manager.ts index d0658f4..50bc1ef 100644 --- a/backend/agent-manager.ts +++ b/backend/agent-manager.ts @@ -76,12 +76,14 @@ export class AgentManager { * @param url * @param username * @param password + * @param name */ - async add(url : string, username : string, password : string) : Promise<Agent> { + async add(url: string, username: string, password: string, name: string): Promise<Agent> { let bean = R.dispense("agent") as Agent; bean.url = url; bean.username = username; bean.password = password; + bean.name = name; await R.store(bean); return bean; } @@ -106,6 +108,23 @@ export class AgentManager { } } + /** + * + * @param url + * @param updatedName + */ + async update(url: string, updatedName: string) { + const agent = await R.findOne("agent", " url = ? ", [ + url, + ]); + if (agent) { + agent.name = updatedName; + await R.store(agent); + } else { + throw new Error("Agent not found"); + } + } + connect(url : string, username : string, password : string) { let obj = new URL(url); let endpoint = obj.host; @@ -278,6 +297,8 @@ export class AgentManager { url: "", username: "", endpoint: "", + name: "", + updatedName: "", }; for (let endpoint in list) { diff --git a/backend/migrations/2023-12-20-2117-agent-table.ts b/backend/migrations/2023-12-20-2117-agent-table.ts index ae18d1d..c06f6f7 100644 --- a/backend/migrations/2023-12-20-2117-agent-table.ts +++ b/backend/migrations/2023-12-20-2117-agent-table.ts @@ -7,6 +7,7 @@ export async function up(knex: Knex): Promise<void> { table.string("url", 255).notNullable().unique(); table.string("username", 255).notNullable(); table.string("password", 255).notNullable(); + table.string("name", 255); table.boolean("active").notNullable().defaultTo(true); }); } diff --git a/backend/models/agent.ts b/backend/models/agent.ts index f806a3d..fff3cb6 100644 --- a/backend/models/agent.ts +++ b/backend/models/agent.ts @@ -23,6 +23,7 @@ export class Agent extends BeanModel { url: this.url, username: this.username, endpoint: this.endpoint, + name: this.name, }; } diff --git a/backend/socket-handlers/manage-agent-socket-handler.ts b/backend/socket-handlers/manage-agent-socket-handler.ts index 6f8fef4..13c7e1b 100644 --- a/backend/socket-handlers/manage-agent-socket-handler.ts +++ b/backend/socket-handlers/manage-agent-socket-handler.ts @@ -20,7 +20,7 @@ export class ManageAgentSocketHandler extends SocketHandler { let data = requestData as LooseObject; let manager = socket.instanceManager; await manager.test(data.url, data.username, data.password); - await manager.add(data.url, data.username, data.password); + await manager.add(data.url, data.username, data.password, data.name); // connect to the agent manager.connect(data.url, data.username, data.password); @@ -66,5 +66,27 @@ export class ManageAgentSocketHandler extends SocketHandler { callbackError(e, callback); } }); + + // updateAgent + socket.on("updateAgent", async (name : string, updatedName : string, callback : unknown) => { + try { + log.debug("manage-agent-socket-handler", "updateAgent"); + checkLogin(socket); + + let manager = socket.instanceManager; + await manager.update(name, updatedName); + + server.disconnectAllSocketClients(undefined, socket.id); + manager.sendAgentList(); + + callbackResult({ + ok: true, + msg: "agentUpdatedSuccessfully", + msgi18n: true, + }, callback); + } catch (e) { + callbackError(e, callback); + } + }); } } diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 708dd4e..c97e380 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -11,8 +11,11 @@ declare module 'vue' { Appearance: typeof import('./src/components/settings/Appearance.vue')['default'] ArrayInput: typeof import('./src/components/ArrayInput.vue')['default'] ArraySelect: typeof import('./src/components/ArraySelect.vue')['default'] + BButton: typeof import('bootstrap-vue-next')['BButton'] BDropdown: typeof import('bootstrap-vue-next')['BDropdown'] BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem'] + BFormGroup: typeof import('bootstrap-vue-next')['BFormGroup'] + BFormInput: typeof import('bootstrap-vue-next')['BFormInput'] BModal: typeof import('bootstrap-vue-next')['BModal'] Confirm: typeof import('./src/components/Confirm.vue')['default'] Container: typeof import('./src/components/Container.vue')['default'] @@ -29,4 +32,7 @@ declare module 'vue' { TwoFADialog: typeof import('./src/components/TwoFADialog.vue')['default'] Uptime: typeof import('./src/components/Uptime.vue')['default'] } + export interface ComponentCustomProperties { + vBModal: typeof import('bootstrap-vue-next')['vBModal'] + } } diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index 0636226..6ecbd5d 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -114,6 +114,8 @@ "removeAgent": "Remove Agent", "removeAgentMsg": "Are you sure you want to remove this agent?", "LongSyntaxNotSupported": "Long syntax is not supported here. Please use the YAML editor.", + "name": "Dockge Agent Display name", + "updatedName": "New Dockge Agent Display name", "Saved": "Saved", "Deployed": "Deployed", "Deleted": "Deleted", diff --git a/frontend/src/mixins/socket.ts b/frontend/src/mixins/socket.ts index b789ff6..15a140a 100644 --- a/frontend/src/mixins/socket.ts +++ b/frontend/src/mixins/socket.ts @@ -131,10 +131,15 @@ export default defineComponent({ methods: { endpointDisplayFunction(endpoint : string) { - if (endpoint) { - return endpoint; - } else { - return this.$t("currentEndpoint"); + for (const [k, v] of Object.entries(this.$data.agentList)) { + if (endpoint) { + if (endpoint === v["endpoint"] && v["name"] !== "") { + return v["name"]; + } + if (endpoint === v["endpoint"] && v["name"] === "" ) { + return endpoint; + } + } } }, diff --git a/frontend/src/pages/Compose.vue b/frontend/src/pages/Compose.vue index bcea292..4038c52 100644 --- a/frontend/src/pages/Compose.vue +++ b/frontend/src/pages/Compose.vue @@ -99,7 +99,7 @@ <label for="name" class="form-label">{{ $t("dockgeAgent") }}</label> <select v-model="stack.endpoint" class="form-select"> <option v-for="(agent, endpoint) in $root.agentList" :key="endpoint" :value="endpoint" :disabled="$root.agentStatusList[endpoint] != 'online'"> - ({{ $root.agentStatusList[endpoint] }}) {{ (endpoint) ? endpoint : $t("currentEndpoint") }} + ({{ $root.agentStatusList[endpoint] }}) {{ (agent.name !== '') ? agent.name : agent.url || $t("Controller") }} </option> </select> </div> diff --git a/frontend/src/pages/DashboardHome.vue b/frontend/src/pages/DashboardHome.vue index a6db753..aa230ce 100644 --- a/frontend/src/pages/DashboardHome.vue +++ b/frontend/src/pages/DashboardHome.vue @@ -49,13 +49,25 @@ </template> <!-- Agent Display Name --> - <span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span> - <a v-else :href="agent.url" target="_blank">{{ endpoint }}</a> + <template v-if="$root.agentStatusList[endpoint]"> + <span v-if="endpoint === '' && agent.name === ''" class="badge bg-secondary me-2">Controller</span> + <span v-else-if="agent.name === ''" :href="agent.url" class="me-2">{{ endpoint }}</span> + <span v-else :href="agent.url" class="me-2">{{ agent.name }}</span> + </template> + + <!-- Edit Name --> + <font-awesome-icon icon="pen-to-square" @click="showEditAgentNameDialog[agent.name] = !showEditAgentNameDialog[agent.Name]" /> + + <!-- Edit Dialog --> + <BModal v-model="showEditAgentNameDialog[agent.name]" :no-close-on-backdrop="true" :close-on-esc="true" :okTitle="$t('Update Name')" okVariant="info" @ok="updateName(agent.url, agent.updatedName)"> + <label for="Update Name" class="form-label">Current value: {{ $t(agent.name) }}</label> + <input id="updatedName" v-model="agent.updatedName" type="text" class="form-control" optional> + </BModal> <!-- Remove Button --> <font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" /> - <!-- Remoe Agent Dialog --> + <!-- Remove Agent Dialog --> <BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)"> <p>{{ agent.url }}</p> {{ $t("removeAgentMsg") }} @@ -81,6 +93,11 @@ <input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password"> </div> + <div class="mb-3"> + <label for="name" class="form-label">{{ $t("Friendly Name") }}</label> + <input id="name" v-model="agent.name" type="text" class="form-control" optional> + </div> + <button type="submit" class="btn btn-primary" :disabled="connectingAgent"> <template v-if="connectingAgent">{{ $t("connecting") }}</template> <template v-else>{{ $t("connect") }}</template> @@ -121,11 +138,14 @@ export default { dockerRunCommand: "", showAgentForm: false, showRemoveAgentDialog: {}, + showEditAgentNameDialog: {}, connectingAgent: false, agent: { url: "http://", username: "", password: "", + name: "", + updatedName: "", } }; }, @@ -199,6 +219,19 @@ export default { }); }, + updateName(url, updatedName) { + this.$root.getSocket().emit("updateAgent", url, updatedName, (res) => { + this.$root.toastRes(res); + + if (res.ok) { + this.showAgentForm = false; + this.agent = { + updatedName: "", + }; + } + }); + }, + getStatusNum(statusName) { let num = 0; @@ -286,7 +319,7 @@ export default { } }, - }, + } }; </script>