diff --git a/web/components.d.ts b/web/components.d.ts index e8e2e378fe1..416f76038ba 100644 --- a/web/components.d.ts +++ b/web/components.d.ts @@ -17,6 +17,7 @@ declare module 'vue' { AdminReposTab: typeof import('./src/components/admin/settings/AdminReposTab.vue')['default'] AdminSecretsTab: typeof import('./src/components/admin/settings/AdminSecretsTab.vue')['default'] AdminUsersTab: typeof import('./src/components/admin/settings/AdminUsersTab.vue')['default'] + AdminVariablesTab: typeof import('./src/components/admin/settings/AdminVariablesTab.vue')['default'] Badge: typeof import('./src/components/atomic/Badge.vue')['default'] BadgeTab: typeof import('./src/components/repo/settings/BadgeTab.vue')['default'] Button: typeof import('./src/components/atomic/Button.vue')['default'] @@ -112,6 +113,10 @@ declare module 'vue' { UserCLIAndAPITab: typeof import('./src/components/user/UserCLIAndAPITab.vue')['default'] UserGeneralTab: typeof import('./src/components/user/UserGeneralTab.vue')['default'] UserSecretsTab: typeof import('./src/components/user/UserSecretsTab.vue')['default'] + UserVariablesTab: typeof import('./src/components/user/UserVariablesTab.vue')['default'] + VariableEdit: typeof import('./src/components/variables/VariableEdit.vue')['default'] + VariableList: typeof import('./src/components/variables/VariableList.vue')['default'] + VariablesTab: typeof import('./src/components/repo/settings/VariablesTab.vue')['default'] Warning: typeof import('./src/components/atomic/Warning.vue')['default'] } } diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index 12333412751..b25ef0e4216 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -275,6 +275,9 @@ "not_allowed": "You are not allowed to access this organization's settings", "secrets": { "desc": "Organization secrets can be passed to all organization's repository individual pipeline steps at runtime as environmental variables." + }, + "variables": { + "desc": "Organization variables can be passed to all organization's repository individual pipeline steps at runtime as environmental variables." } } }, @@ -285,6 +288,10 @@ "desc": "Global secrets can be passed to all repositories individual pipeline steps at runtime as environmental variables.", "warning": "These secrets will be available for all server users." }, + "variables": { + "desc": "Global variables can be passed to all repositories individual pipeline steps at runtime as environmental variables.", + "warning": "These variables will be available for all server users." + }, "agents": { "agents": "Agents", "desc": "Agents registered for this server", @@ -406,8 +413,13 @@ } }, "secrets": { + "secrets": "Secrets", "desc": "User secrets can be passed to all user's repository individual pipeline steps at runtime as environmental variables." }, + "variables": { + "variables": "Variables", + "desc": "User variables can be passed to all user's repository individual pipeline steps at runtime as environmental variables." + }, "cli_and_api": { "cli_and_api": "CLI & API", "desc": "Personal Access Token, CLI and API usage", @@ -423,6 +435,22 @@ "internal_error": "Some internal error occurred", "access_denied": "You are not allowed to login" }, + "variables": { + "variables": "Variables", + "desc": "Variables can be passed to individual pipeline steps at runtime as environmental variables.", + "none": "There are no variables yet.", + "add": "Add variable", + "save": "Save variable", + "show": "Show variables", + "name": "Name", + "value": "Value", + "delete_confirm": "Do you really want to delete this variable?", + "deleted": "Variable deleted", + "created": "Variable created", + "saved": "Variable saved", + "edit": "Edit variable", + "delete": "Delete variable" + }, "secrets": { "secrets": "Secrets", "desc": "Secrets can be passed to individual pipeline steps at runtime as environmental variables.", diff --git a/web/src/components/admin/settings/AdminVariablesTab.vue b/web/src/components/admin/settings/AdminVariablesTab.vue new file mode 100644 index 00000000000..0d67c845ad8 --- /dev/null +++ b/web/src/components/admin/settings/AdminVariablesTab.vue @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + diff --git a/web/src/components/repo/settings/VariablesTab.vue b/web/src/components/repo/settings/VariablesTab.vue new file mode 100644 index 00000000000..5415302a425 --- /dev/null +++ b/web/src/components/repo/settings/VariablesTab.vue @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + diff --git a/web/src/components/user/UserSecretsTab.vue b/web/src/components/user/UserSecretsTab.vue index 1288a5a8cad..21583a3b5d4 100644 --- a/web/src/components/user/UserSecretsTab.vue +++ b/web/src/components/user/UserSecretsTab.vue @@ -11,17 +11,16 @@ - + + + + + {{ $t('user.settings.variables.variables') }} + + {{ $t('user.settings.variables.desc') }} + + + + + + + + + + + + + + diff --git a/web/src/components/variables/VariableEdit.vue b/web/src/components/variables/VariableEdit.vue new file mode 100644 index 00000000000..9542ae44adc --- /dev/null +++ b/web/src/components/variables/VariableEdit.vue @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/components/variables/VariableList.vue b/web/src/components/variables/VariableList.vue new file mode 100644 index 00000000000..e3e357085f2 --- /dev/null +++ b/web/src/components/variables/VariableList.vue @@ -0,0 +1,70 @@ + + + + {{ variable.name }} + + + + + + + + {{ $t('variables.none') }} + + + + diff --git a/web/src/lib/api/index.ts b/web/src/lib/api/index.ts index 13fdde5ccf8..cf931d2d651 100644 --- a/web/src/lib/api/index.ts +++ b/web/src/lib/api/index.ts @@ -16,6 +16,7 @@ import { RepoSettings, Secret, User, + Variable, } from './types'; type RepoListOptions = { @@ -148,6 +149,25 @@ export default class WoodpeckerClient extends ApiClient { return this._delete(`/api/repos/${repoId}/logs/${pipeline}/${step}`); } + getVariablesList(repoId: number, opts?: PaginationOptions): Promise { + const query = encodeQueryString(opts); + return this._get(`/api/repos/${repoId}/variables?${query}`) as Promise; + } + + createVariable(repoId: number, variable: Partial): Promise { + return this._post(`/api/repos/${repoId}/variables`, variable); + } + + updateVariable(repoId: number, variable: Partial): Promise { + const variableName = encodeURIComponent(variable.name ?? ''); + return this._patch(`/api/repos/${repoId}/variables/${variableName}`, variable); + } + + deleteVariable(repoId: number, variableName: string): Promise { + const name = encodeURIComponent(variableName); + return this._delete(`/api/repos/${repoId}/variables/${name}`); + } + getSecretList(repoId: number, opts?: PaginationOptions): Promise { const query = encodeQueryString(opts); return this._get(`/api/repos/${repoId}/secrets?${query}`) as Promise; @@ -217,6 +237,25 @@ export default class WoodpeckerClient extends ApiClient { return this._get(`/api/orgs/${orgId}/permissions`) as Promise; } + getOrgVariablesList(orgId: number, opts?: PaginationOptions): Promise { + const query = encodeQueryString(opts); + return this._get(`/api/orgs/${orgId}/variables?${query}`) as Promise; + } + + createOrgVariable(orgId: number, variable: Partial): Promise { + return this._post(`/api/orgs/${orgId}/variables`, variable); + } + + updateOrgVariable(orgId: number, variable: Partial): Promise { + const variableName = encodeURIComponent(variable.name ?? ''); + return this._patch(`/api/orgs/${orgId}/variables/${variableName}`, variable); + } + + deleteOrgVariable(orgId: number, variableName: string): Promise { + const name = encodeURIComponent(variableName); + return this._delete(`/api/orgs/${orgId}/variables/${name}`); + } + getOrgSecretList(orgId: number, opts?: PaginationOptions): Promise { const query = encodeQueryString(opts); return this._get(`/api/orgs/${orgId}/secrets?${query}`) as Promise; @@ -236,6 +275,25 @@ export default class WoodpeckerClient extends ApiClient { return this._delete(`/api/orgs/${orgId}/secrets/${name}`); } + getGlobalVariableList(opts?: PaginationOptions): Promise { + const query = encodeQueryString(opts); + return this._get(`/api/variables?${query}`) as Promise; + } + + createGlobalVariable(variable: Partial): Promise { + return this._post(`/api/variables`, variable); + } + + updateGlobalVariable(variable: Partial): Promise { + const variableName = encodeURIComponent(variable.name ?? ''); + return this._patch(`/api/variables/${variableName}`, variable); + } + + deleteGlobalVariable(variableName: string): Promise { + const name = encodeURIComponent(variableName); + return this._delete(`/api/variables/${name}`); + } + getGlobalSecretList(opts?: PaginationOptions): Promise { const query = encodeQueryString(opts); return this._get(`/api/secrets?${query}`) as Promise; diff --git a/web/src/lib/api/types/index.ts b/web/src/lib/api/types/index.ts index 7918c24d775..53c73906539 100644 --- a/web/src/lib/api/types/index.ts +++ b/web/src/lib/api/types/index.ts @@ -9,4 +9,5 @@ export * from './registry'; export * from './repo'; export * from './secret'; export * from './user'; +export * from './variable'; export * from './webhook'; diff --git a/web/src/lib/api/types/variable.ts b/web/src/lib/api/types/variable.ts new file mode 100644 index 00000000000..f144a432090 --- /dev/null +++ b/web/src/lib/api/types/variable.ts @@ -0,0 +1,7 @@ +export type Variable = { + id: string; + repo_id: number; + org_id: number; + name: string; + value: string; +}; diff --git a/web/src/views/User.vue b/web/src/views/User.vue index 50e5210e1bb..fe66653c368 100644 --- a/web/src/views/User.vue +++ b/web/src/views/User.vue @@ -5,6 +5,9 @@ + + + @@ -20,6 +23,7 @@ import Tab from '~/components/layout/scaffold/Tab.vue'; import UserCLIAndAPITab from '~/components/user/UserCLIAndAPITab.vue'; import UserGeneralTab from '~/components/user/UserGeneralTab.vue'; import UserSecretsTab from '~/components/user/UserSecretsTab.vue'; +import UserVariablesTab from '~/components/user/UserVariablesTab.vue'; import useConfig from '~/compositions/useConfig'; const address = `${window.location.protocol}//${window.location.host}${useConfig().rootPath}`; // port is included in location.host diff --git a/web/src/views/admin/AdminSettings.vue b/web/src/views/admin/AdminSettings.vue index 9fbfa74647e..c0668c9d2bd 100644 --- a/web/src/views/admin/AdminSettings.vue +++ b/web/src/views/admin/AdminSettings.vue @@ -6,6 +6,9 @@ + + + @@ -39,6 +42,7 @@ import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue'; import AdminReposTab from '~/components/admin/settings/AdminReposTab.vue'; import AdminSecretsTab from '~/components/admin/settings/AdminSecretsTab.vue'; import AdminUsersTab from '~/components/admin/settings/AdminUsersTab.vue'; +import AdminVariablesTab from '~/components/admin/settings/AdminVariablesTab.vue'; import Scaffold from '~/components/layout/scaffold/Scaffold.vue'; import Tab from '~/components/layout/scaffold/Tab.vue'; import useAuthentication from '~/compositions/useAuthentication'; diff --git a/web/src/views/repo/RepoSettings.vue b/web/src/views/repo/RepoSettings.vue index bcc070c5d7a..0a41bf29544 100644 --- a/web/src/views/repo/RepoSettings.vue +++ b/web/src/views/repo/RepoSettings.vue @@ -17,6 +17,9 @@ + + + @@ -48,6 +51,7 @@ import CronTab from '~/components/repo/settings/CronTab.vue'; import GeneralTab from '~/components/repo/settings/GeneralTab.vue'; import RegistriesTab from '~/components/repo/settings/RegistriesTab.vue'; import SecretsTab from '~/components/repo/settings/SecretsTab.vue'; +import VariablesTab from '~/components/repo/settings/VariablesTab.vue'; import useNotifications from '~/compositions/useNotifications'; import { useRouteBack } from '~/compositions/useRouteBack'; import { Repo, RepoPermissions } from '~/lib/api/types';
+ {{ $t('user.settings.variables.desc') }} + +