Add editing of secrets and registries (#823)

This commit is contained in:
Anbraten 2022-03-02 00:19:33 +01:00 committed by GitHub
parent da99f47553
commit 2f6f44417d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 36 deletions

View file

@ -25,6 +25,7 @@
"fuse.js": "6.4.6",
"humanize-duration": "3.27.0",
"javascript-time-ago": "2.3.10",
"lodash": "4.17.21",
"node-emoji": "1.11.0",
"pinia": "2.0.0",
"vue": "v3.2.20",
@ -34,6 +35,7 @@
"@iconify/json": "1.1.421",
"@types/humanize-duration": "3.27.0",
"@types/javascript-time-ago": "2.0.3",
"@types/lodash": "4.14.179",
"@types/node": "16.11.6",
"@types/node-emoji": "1.8.1",
"@typescript-eslint/eslint-plugin": "5.6.0",

View file

@ -32,9 +32,9 @@
@click="doClick"
>
<slot>
<Icon v-if="startIcon" :name="startIcon" class="mr-1" :class="{ invisible: isLoading }" />
<Icon v-if="startIcon" :name="startIcon" class="mr-1 !w-6 !h-6" :class="{ invisible: isLoading }" />
<span :class="{ invisible: isLoading }">{{ text }}</span>
<Icon v-if="endIcon" :name="endIcon" class="ml-2" :class="{ invisible: isLoading }" />
<Icon v-if="endIcon" :name="endIcon" class="ml-2 w-6 h-6" :class="{ invisible: isLoading }" />
<div
class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"
:class="{

View file

@ -33,6 +33,7 @@
<i-bx-bx-power-off v-else-if="name === 'turn-off'" class="h-6 w-6" />
<i-mdi-chevron-right v-else-if="name === 'chevron-right'" class="h-6 w-6" />
<i-carbon-close-outline v-else-if="name === 'close'" class="h-6 w-6" />
<i-ic-baseline-edit v-else-if="name === 'edit'" class="h-6 w-6" />
<div v-else-if="name === 'blank'" class="h-6 w-6" />
</template>
@ -74,7 +75,8 @@ export type IconNames =
| 'heal'
| 'chevron-right'
| 'turn-off'
| 'close';
| 'close'
| 'edit';
export default defineComponent({
name: 'Icon',

View file

@ -22,6 +22,7 @@
focus:outline-none focus:border-blue-400
dark:placeholder-gray-600 dark:text-gray-500
"
:disabled="disabled"
:type="type"
:placeholder="placeholder"
/>
@ -36,6 +37,7 @@
focus:outline-none focus:border-blue-400
dark:placeholder-gray-600 dark:text-gray-500
"
:disabled="disabled"
:placeholder="placeholder"
:rows="lines"
/>
@ -70,6 +72,10 @@ export default defineComponent({
type: Number,
default: 1,
},
disabled: {
type: Boolean,
},
},
emits: {

View file

@ -9,21 +9,22 @@
</p>
</div>
<Button
v-if="showAddRegistry"
v-if="selectedRegistry"
class="ml-auto"
start-icon="list"
start-icon="back"
text="Show registries"
@click="showAddRegistry = false"
@click="selectedRegistry = undefined"
/>
<Button v-else class="ml-auto" start-icon="plus" text="Add registry" @click="showAddRegistry = true" />
<Button v-else class="ml-auto" start-icon="plus" text="Add registry" @click="selectedRegistry = {}" />
</div>
<div v-if="!showAddRegistry" class="space-y-4 text-gray-500">
<div v-if="!selectedRegistry" class="space-y-4 text-gray-500">
<ListItem v-for="registry in registries" :key="registry.id" class="items-center">
<span>{{ registry.address }}</span>
<IconButton icon="edit" class="ml-auto w-8 h-8" @click="selectedRegistry = registry" />
<IconButton
icon="trash"
class="ml-auto w-8 h-8 hover:text-red-400"
class="w-8 h-8 hover:text-red-400"
:is-loading="isDeleting"
@click="deleteRegistry(registry)"
/>
@ -36,7 +37,12 @@
<form @submit.prevent="createRegistry">
<InputField label="Address">
<!-- TODO: check input field Address is a valid address -->
<TextField v-model="selectedRegistry.address" placeholder="Registry Address (e.g. docker.io)" required />
<TextField
v-model="selectedRegistry.address"
placeholder="Registry Address (e.g. docker.io)"
required
:disabled="isEditingRegistry"
/>
</InputField>
<InputField label="Username">
@ -47,14 +53,14 @@
<TextField v-model="selectedRegistry.password" placeholder="Password" required />
</InputField>
<Button type="submit" :is-loading="isSaving" text="Add registry" />
<Button type="submit" :is-loading="isSaving" :text="isEditingRegistry ? 'Save registy' : 'Add registry'" />
</form>
</div>
</Panel>
</template>
<script lang="ts">
import { defineComponent, inject, onMounted, Ref, ref } from 'vue';
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
import Button from '~/components/atomic/Button.vue';
import DocsLink from '~/components/atomic/DocsLink.vue';
@ -88,8 +94,8 @@ export default defineComponent({
const repo = inject<Ref<Repo>>('repo');
const registries = ref<Registry[]>();
const showAddRegistry = ref(false);
const selectedRegistry = ref<Partial<Registry>>({});
const selectedRegistry = ref<Partial<Registry>>();
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries() {
if (!repo?.value) {
@ -104,10 +110,17 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry");
}
if (isEditingRegistry.value) {
await apiClient.updateRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
} else {
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
}
notifications.notify({ title: 'Registry credentials created', type: 'success' });
showAddRegistry.value = false;
selectedRegistry.value = {};
selectedRegistry.value = undefined;
await loadRegistries();
});
@ -126,7 +139,7 @@ export default defineComponent({
await loadRegistries();
});
return { selectedRegistry, registries, showAddRegistry, isSaving, isDeleting, createRegistry, deleteRegistry };
return { selectedRegistry, registries, isEditingRegistry, isSaving, isDeleting, createRegistry, deleteRegistry };
},
});
</script>

View file

@ -9,16 +9,16 @@
</p>
</div>
<Button
v-if="showAddSecret"
v-if="selectedSecret"
class="ml-auto"
text="Show secrets"
start-icon="list"
@click="showAddSecret = false"
start-icon="back"
@click="selectedSecret = undefined"
/>
<Button v-else class="ml-auto" text="Add secret" start-icon="plus" @click="showAddSecret = true" />
<Button v-else class="ml-auto" text="Add secret" start-icon="plus" @click="showAddSecret" />
</div>
<div v-if="!showAddSecret" class="space-y-4 text-gray-500">
<div v-if="!selectedSecret" class="space-y-4 text-gray-500">
<ListItem v-for="secret in secrets" :key="secret.id" class="items-center">
<span>{{ secret.name }}</span>
<div class="ml-auto">
@ -29,6 +29,7 @@
>{{ event }}</span
>
</div>
<IconButton icon="edit" class="ml-2 w-8 h-8" @click="selectedSecret = secret" />
<IconButton
icon="trash"
class="ml-2 w-8 h-8 hover:text-red-400"
@ -43,7 +44,7 @@
<div v-else class="space-y-4">
<form @submit.prevent="createSecret">
<InputField label="Name">
<TextField v-model="selectedSecret.name" placeholder="Name" required />
<TextField v-model="selectedSecret.name" placeholder="Name" required :disabled="isEditingSecret" />
</InputField>
<InputField label="Value">
@ -61,14 +62,15 @@
<CheckboxesField v-model="selectedSecret.event" :options="secretEventsOptions" />
</InputField>
<Button :is-loading="isSaving" type="submit" text="Add secret" />
<Button :is-loading="isSaving" type="submit" :text="isEditingSecret ? 'Save secret' : 'Add secret'" />
</form>
</div>
</Panel>
</template>
<script lang="ts">
import { defineComponent, inject, onMounted, Ref, ref } from 'vue';
import { cloneDeep } from 'lodash';
import { computed, defineComponent, inject, onMounted, Ref, ref } from 'vue';
import Button from '~/components/atomic/Button.vue';
import DocsLink from '~/components/atomic/DocsLink.vue';
@ -123,9 +125,18 @@ export default defineComponent({
const repo = inject<Ref<Repo>>('repo');
const secrets = ref<Secret[]>();
const showAddSecret = ref(false);
const selectedSecret = ref<Partial<Secret>>({ ...emptySecret });
const images = ref('');
const selectedSecret = ref<Partial<Secret>>();
const isEditingSecret = computed(() => !!selectedSecret.value?.id);
const images = computed<string>({
get() {
return selectedSecret.value?.image?.join(',') || '';
},
set(value) {
if (selectedSecret.value) {
selectedSecret.value.image = value.split(',').map((s) => s.trim());
}
},
});
async function loadSecrets() {
if (!repo?.value) {
@ -140,12 +151,17 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
const imageList = images.value.split(',').map((s) => s.trim());
selectedSecret.value.image = imageList.filter((s) => s !== '');
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret");
}
if (isEditingSecret.value) {
await apiClient.updateSecret(repo.value.owner, repo.value.name, selectedSecret.value);
} else {
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
}
notifications.notify({ title: 'Secret created', type: 'success' });
showAddSecret.value = false;
selectedSecret.value = { ...emptySecret };
selectedSecret.value = undefined;
await loadSecrets();
});
@ -159,6 +175,10 @@ export default defineComponent({
await loadSecrets();
});
function showAddSecret() {
selectedSecret.value = cloneDeep(emptySecret);
}
onMounted(async () => {
await loadSecrets();
});
@ -168,9 +188,10 @@ export default defineComponent({
selectedSecret,
secrets,
images,
showAddSecret,
isEditingSecret,
isSaving,
isDeleting,
showAddSecret,
createSecret,
deleteSecret,
};

View file

@ -111,6 +111,10 @@ export default class WoodpeckerClient extends ApiClient {
return this._post(`/api/repos/${owner}/${repo}/secrets`, secret);
}
updateSecret(owner: string, repo: string, secret: Partial<Secret>): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}/secrets/${secret.name}`, secret);
}
deleteSecret(owner: string, repo: string, secretName: string): Promise<unknown> {
return this._delete(`/api/repos/${owner}/${repo}/secrets/${secretName}`);
}
@ -123,6 +127,10 @@ export default class WoodpeckerClient extends ApiClient {
return this._post(`/api/repos/${owner}/${repo}/registry`, registry);
}
updateRegistry(owner: string, repo: string, registry: Partial<Registry>): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}/registry/${registry.address}`, registry);
}
deleteRegistry(owner: string, repo: string, registryAddress: string): Promise<unknown> {
return this._delete(`/api/repos/${owner}/${repo}/registry/${registryAddress}`);
}

View file

@ -200,6 +200,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/lodash@4.14.179":
version "4.14.179"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5"
integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==
"@types/node-emoji@1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@types/node-emoji/-/node-emoji-1.8.1.tgz#689cb74fdf6e84309bcafce93a135dfecd01de3f"
@ -1958,7 +1963,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@^4.17.19, lodash@^4.17.21:
lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==