mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 03:11:01 +00:00
Add user registries UI (#3888)
This commit is contained in:
parent
2d607a9ae4
commit
a26c7a475b
4 changed files with 124 additions and 2 deletions
5
web/components.d.ts
vendored
5
web/components.d.ts
vendored
|
@ -67,12 +67,12 @@ declare module 'vue' {
|
|||
IMdiPlay: typeof import('~icons/mdi/play')['default']
|
||||
IMdiRadioboxBlank: typeof import('~icons/mdi/radiobox-blank')['default']
|
||||
IMdiRadioboxIndeterminateVariant: typeof import('~icons/mdi/radiobox-indeterminate-variant')['default']
|
||||
IMdiSync: typeof import('~icons/mdi/sync')['default']
|
||||
IMdiSourceBranch: typeof import('~icons/mdi/source-branch')['default']
|
||||
IMdiSourceCommit: typeof import('~icons/mdi/source-commit')['default']
|
||||
IMdiSourceMerge: typeof import('~icons/mdi/source-merge')['default']
|
||||
IMdiSourcePull: typeof import('~icons/mdi/source-pull')['default']
|
||||
IMdiStop: typeof import('~icons/mdi/stop')['default']
|
||||
IMdiSync: typeof import('~icons/mdi/sync')['default']
|
||||
IMdiTagOutline: typeof import('~icons/mdi/tag-outline')['default']
|
||||
InputField: typeof import('./src/components/form/InputField.vue')['default']
|
||||
IPhGitlabLogoSimpleFill: typeof import('~icons/ph/gitlab-logo-simple-fill')['default']
|
||||
|
@ -100,8 +100,8 @@ declare module 'vue' {
|
|||
PipelineStepList: typeof import('./src/components/repo/pipeline/PipelineStepList.vue')['default']
|
||||
Popup: typeof import('./src/components/layout/Popup.vue')['default']
|
||||
RadioField: typeof import('./src/components/form/RadioField.vue')['default']
|
||||
RegistryEdit: typeof import('./src/components/registry/RegistryEdit.vue')['default']
|
||||
RegistriesTab: typeof import('./src/components/repo/settings/RegistriesTab.vue')['default']
|
||||
RegistryEdit: typeof import('./src/components/registry/RegistryEdit.vue')['default']
|
||||
RegistryList: typeof import('./src/components/registry/RegistryList.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
@ -116,6 +116,7 @@ declare module 'vue' {
|
|||
TextField: typeof import('./src/components/form/TextField.vue')['default']
|
||||
UserCLIAndAPITab: typeof import('./src/components/user/UserCLIAndAPITab.vue')['default']
|
||||
UserGeneralTab: typeof import('./src/components/user/UserGeneralTab.vue')['default']
|
||||
UserRegistriesTab: typeof import('./src/components/user/UserRegistriesTab.vue')['default']
|
||||
UserSecretsTab: typeof import('./src/components/user/UserSecretsTab.vue')['default']
|
||||
Warning: typeof import('./src/components/atomic/Warning.vue')['default']
|
||||
}
|
||||
|
|
|
@ -388,6 +388,9 @@
|
|||
"secrets": {
|
||||
"desc": "User secrets can be passed to all user's repository individual pipeline steps at runtime as environmental variables."
|
||||
},
|
||||
"registries": {
|
||||
"desc": "User registries credentials can be added to use private images for all individual pipelines."
|
||||
},
|
||||
"cli_and_api": {
|
||||
"cli_and_api": "CLI & API",
|
||||
"desc": "Personal Access Token, CLI and API usage",
|
||||
|
|
114
web/src/components/user/UserRegistriesTab.vue
Normal file
114
web/src/components/user/UserRegistriesTab.vue
Normal file
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<Panel>
|
||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-wp-background-100">
|
||||
<div class="ml-2">
|
||||
<h1 class="text-xl text-wp-text-100">{{ $t('registries.registries') }}</h1>
|
||||
<p class="text-sm text-wp-text-alt-100">
|
||||
{{ $t('user.settings.registries.desc') }}
|
||||
<DocsLink :topic="$t('registries.registries')" url="docs/usage/registries" />
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
class="ml-auto"
|
||||
:text="$t('registries.show')"
|
||||
start-icon="back"
|
||||
@click="selectedRegistry = undefined"
|
||||
/>
|
||||
<Button v-else class="ml-auto" :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</div>
|
||||
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
v-model="registries"
|
||||
:is-deleting="isDeleting"
|
||||
@edit="editRegistry"
|
||||
@delete="deleteRegistry"
|
||||
/>
|
||||
|
||||
<RegistryEdit
|
||||
v-else
|
||||
v-model="selectedRegistry"
|
||||
:is-saving="isSaving"
|
||||
@save="createRegistry"
|
||||
@cancel="selectedRegistry = undefined"
|
||||
/>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { usePagination } from '~/compositions/usePaginate';
|
||||
import type { Registry } from '~/lib/api/types';
|
||||
|
||||
const emptyRegistry: Partial<Registry> = {
|
||||
address: '',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
|
||||
const { user } = useAuthentication();
|
||||
if (!user) {
|
||||
throw new Error('Unexpected: Unauthenticated');
|
||||
}
|
||||
const selectedRegistry = ref<Partial<Registry>>();
|
||||
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
|
||||
|
||||
async function loadRegistries(page: number): Promise<Registry[] | null> {
|
||||
if (!user) {
|
||||
throw new Error('Unexpected: Unauthenticated');
|
||||
}
|
||||
|
||||
return apiClient.getOrgRegistryList(user.org_id, { page });
|
||||
}
|
||||
|
||||
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
|
||||
|
||||
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
|
||||
if (!selectedRegistry.value) {
|
||||
throw new Error("Unexpected: Can't get registry");
|
||||
}
|
||||
|
||||
if (isEditingRegistry.value) {
|
||||
await apiClient.updateOrgRegistry(user.org_id, selectedRegistry.value);
|
||||
} else {
|
||||
await apiClient.createOrgRegistry(user.org_id, selectedRegistry.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: isEditingRegistry.value ? i18n.t('registries.saved') : i18n.t('registries.created'),
|
||||
type: 'success',
|
||||
});
|
||||
selectedRegistry.value = undefined;
|
||||
resetPage();
|
||||
});
|
||||
|
||||
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
|
||||
await apiClient.deleteOrgRegistry(user.org_id, _registry.address);
|
||||
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
function editRegistry(registry: Registry) {
|
||||
selectedRegistry.value = cloneDeep(registry);
|
||||
}
|
||||
|
||||
function showAddRegistry() {
|
||||
selectedRegistry.value = cloneDeep(emptyRegistry);
|
||||
}
|
||||
</script>
|
|
@ -8,6 +8,9 @@
|
|||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<UserSecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<UserRegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
|
||||
<UserCLIAndAPITab />
|
||||
</Tab>
|
||||
|
@ -19,6 +22,7 @@ import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
|||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import UserCLIAndAPITab from '~/components/user/UserCLIAndAPITab.vue';
|
||||
import UserGeneralTab from '~/components/user/UserGeneralTab.vue';
|
||||
import UserRegistriesTab from '~/components/user/UserRegistriesTab.vue';
|
||||
import UserSecretsTab from '~/components/user/UserSecretsTab.vue';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
|
||||
|
|
Loading…
Reference in a new issue