From d467a06e728757eb5ea75792324ecca5c333f81f Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 5 Apr 2026 20:09:35 +0200 Subject: [PATCH] feat(frontend): add bot settings page and services --- frontend/src/components/misc/User.vue | 71 ++-- .../src/components/token/ApiTokenForm.vue | 402 ++++++++++++++++++ frontend/src/i18n/lang/en.json | 16 +- frontend/src/modelTypes/IApiToken.ts | 1 + frontend/src/models/apiTokenModel.ts | 1 + frontend/src/router/index.ts | 5 + frontend/src/services/botUser.ts | 19 + frontend/src/views/user/Settings.vue | 6 + .../src/views/user/settings/ApiTokens.vue | 381 ++--------------- frontend/src/views/user/settings/BotUsers.vue | 362 ++++++++++++++++ pkg/models/api_tokens.go | 2 +- 11 files changed, 885 insertions(+), 381 deletions(-) create mode 100644 frontend/src/components/token/ApiTokenForm.vue create mode 100644 frontend/src/services/botUser.ts create mode 100644 frontend/src/views/user/settings/BotUsers.vue diff --git a/frontend/src/components/misc/User.vue b/frontend/src/components/misc/User.vue index 5570f11d6..b7348b1a5 100644 --- a/frontend/src/components/misc/User.vue +++ b/frontend/src/components/misc/User.vue @@ -3,27 +3,32 @@ class="user" :class="{'is-inline': isInline}" > - + + + B + {{ displayName }} - {{ $t('user.bot.badge') }} + + diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 07f5d376a..8af6ae590 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -59,9 +59,6 @@ "text": "Please check your network connection and try again." }, "user": { - "bot": { - "badge": "Bot" - }, "auth": { "username": "Username", "usernameEmail": "Username Or Email Address", @@ -110,6 +107,19 @@ "registrationFailed": "An error occurred during registration. Please check your input and try again." }, "settings": { + "bots": { + "title": "Bot Users", + "description": "Bot users are API-only users you own. They can be added to projects, assigned tasks, and authenticated with API tokens. They cannot log in interactively.", + "namePlaceholder": "My Assistant", + "create": "Create bot", + "enable": "Enable", + "badge": "Bot", + "delete": { + "header": "Delete this bot user", + "text1": "Are you sure you want to delete the bot user \"{username}\"?", + "text2": "This is irreversible. Any API tokens belonging to this bot will be revoked." + } + }, "title": "Settings", "newPasswordTitle": "Update Your Password", "newPassword": "New password", diff --git a/frontend/src/modelTypes/IApiToken.ts b/frontend/src/modelTypes/IApiToken.ts index 189549c4d..15773df81 100644 --- a/frontend/src/modelTypes/IApiToken.ts +++ b/frontend/src/modelTypes/IApiToken.ts @@ -11,4 +11,5 @@ export interface IApiToken extends IAbstract { permissions: IApiPermission expiresAt: Date created: Date + ownerId?: number } diff --git a/frontend/src/models/apiTokenModel.ts b/frontend/src/models/apiTokenModel.ts index a23227004..096ca5f28 100644 --- a/frontend/src/models/apiTokenModel.ts +++ b/frontend/src/models/apiTokenModel.ts @@ -8,6 +8,7 @@ export default class ApiTokenModel extends AbstractModel { permissions = null expiresAt: Date = null created: Date = null + ownerId = 0 constructor(data: Partial = {}) { super() diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 23585e6f5..6d48058af 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -163,6 +163,11 @@ const router = createRouter({ name: 'user.settings.webhooks', component: () => import('@/views/user/settings/Webhooks.vue'), }, + { + path: '/user/settings/bots', + name: 'user.settings.bots', + component: () => import('@/views/user/settings/BotUsers.vue'), + }, { path: '/user/settings/migrate', name: 'migrate.start', diff --git a/frontend/src/services/botUser.ts b/frontend/src/services/botUser.ts new file mode 100644 index 000000000..6e18eef35 --- /dev/null +++ b/frontend/src/services/botUser.ts @@ -0,0 +1,19 @@ +import AbstractService from '@/services/abstractService' +import type {IUser} from '@/modelTypes/IUser' +import UserModel from '@/models/user' + +export default class BotUserService extends AbstractService { + constructor() { + super({ + create: '/user/bots', + getAll: '/user/bots', + get: '/user/bots/{id}', + update: '/user/bots/{id}', + delete: '/user/bots/{id}', + }) + } + + modelFactory(data: Partial) { + return new UserModel(data) + } +} diff --git a/frontend/src/views/user/Settings.vue b/frontend/src/views/user/Settings.vue index da9129258..9ac8688d0 100644 --- a/frontend/src/views/user/Settings.vue +++ b/frontend/src/views/user/Settings.vue @@ -26,6 +26,7 @@ const migratorsEnabled = computed(() => configStore.migratorsEnabled) const isLocalUser = computed(() => authStore.info?.isLocalUser) const userDeletionEnabled = computed(() => configStore.userDeletionEnabled) const webhooksEnabled = computed(() => configStore.webhooksEnabled) +const botUsersEnabled = computed(() => configStore.botUsersEnabled) const navigationItems = computed(() => { const items = [ @@ -80,6 +81,11 @@ const navigationItems = computed(() => { routeName: 'user.settings.webhooks', condition: webhooksEnabled.value, }, + { + title: t('user.settings.bots.title'), + routeName: 'user.settings.bots', + condition: botUsersEnabled.value, + }, { title: t('user.deletion.title'), routeName: 'user.settings.deletion', diff --git a/frontend/src/views/user/settings/ApiTokens.vue b/frontend/src/views/user/settings/ApiTokens.vue index bbbfab9cc..a9506d8e1 100644 --- a/frontend/src/views/user/settings/ApiTokens.vue +++ b/frontend/src/views/user/settings/ApiTokens.vue @@ -1,35 +1,19 @@ @@ -354,132 +141,14 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) { -
- - - - -
- -
-
- -
- -
-
- - -
- -

{{ $t('user.settings.apiTokens.permissionExplanation') }}

- - -
- -
- - {{ $t(`user.settings.apiTokens.presets.${preset.id}`) }} - -
-
- -
- - -
-
- -

- {{ $t('user.settings.apiTokens.permissionRequired') }} -

- - {{ $t('user.settings.apiTokens.createToken') }} - - + :loading="service.loading" + :initial-title="initialTitle" + :initial-scopes="initialScopes" + @created="onTokenCreated" + @cancel="showCreateForm = false" + /> + + diff --git a/frontend/src/views/user/settings/BotUsers.vue b/frontend/src/views/user/settings/BotUsers.vue new file mode 100644 index 000000000..bb62c90e2 --- /dev/null +++ b/frontend/src/views/user/settings/BotUsers.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/pkg/models/api_tokens.go b/pkg/models/api_tokens.go index ed7651b9e..3664f1b3a 100644 --- a/pkg/models/api_tokens.go +++ b/pkg/models/api_tokens.go @@ -56,7 +56,7 @@ type APIToken struct { // The user ID of the token owner. When creating a token for a bot user, set this // to the bot's ID. If omitted, defaults to the authenticated user. - OwnerID int64 `xorm:"bigint not null" json:"owner_id,omitempty"` + OwnerID int64 `xorm:"bigint not null" json:"owner_id,omitempty" query:"owner_id"` web.Permissions `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`