mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 03:36:04 +08:00
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
+191
@@ -0,0 +1,191 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||
|
||||
const _NEW = '_NEW';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
ModalWithCard,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [Array, String],
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
resource: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
userOptions: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create',
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedUser: this.value,
|
||||
newUsername: '',
|
||||
randomStr: randomStr(5).toLowerCase(),
|
||||
errors: [],
|
||||
isOpen: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
||||
|
||||
watch: {
|
||||
value(neu) {
|
||||
this.selectedUser = neu;
|
||||
},
|
||||
|
||||
selectedUser(val, old) {
|
||||
if ( val.includes(_NEW)) {
|
||||
this.$set(this, 'selectedUser', old);
|
||||
this.update();
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.$emit('update:user', clone(this.selectedUser));
|
||||
},
|
||||
|
||||
addUser(buttonCb) {
|
||||
const reg = /^[-._0-9a-zA-Z]+$/;
|
||||
|
||||
this.errors = [];
|
||||
|
||||
if (!reg.test(this.newUsername)) {
|
||||
const message = this.t('harvester.virtualMachine.accessCredentials.invalidUser');
|
||||
|
||||
this.errors.push(message);
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.userOptions.includes(this.newUsername)) {
|
||||
const message = this.t('harvester.virtualMachine.accessCredentials.duplicatedUser');
|
||||
|
||||
this.errors.push(message);
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
this.selectedUser.push(this.newUsername);
|
||||
} else {
|
||||
this.selectedUser = this.newUsername;
|
||||
}
|
||||
|
||||
this.$emit('update:newUser', this.newUsername);
|
||||
this.update();
|
||||
|
||||
buttonCb(true);
|
||||
this.cancel();
|
||||
},
|
||||
|
||||
show() {
|
||||
this.isOpen = true;
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.isOpen = false;
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.hide();
|
||||
this.newUsername = '';
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vm__credentials-users">
|
||||
<LabeledSelect
|
||||
v-model="selectedUser"
|
||||
:options="userOptions"
|
||||
:label="t('harvester.virtualMachine.accessCredentials.injectSSH.users')"
|
||||
:taggable="multiple"
|
||||
:mode="mode"
|
||||
:multiple="multiple"
|
||||
:searchable="false"
|
||||
:tooltip="t('harvester.virtualMachine.accessCredentials.userTips')"
|
||||
:class="{'hasUsers': selectedUser.length > 0}"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<ModalWithCard
|
||||
v-if="isOpen"
|
||||
:name="randomStr"
|
||||
width="40%"
|
||||
:errors="errors"
|
||||
@finish="addUser"
|
||||
@close="cancel"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('harvester.virtualMachine.accessCredentials.injectSSH.addUser') }}
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<LabeledInput
|
||||
v-model="newUsername"
|
||||
:label="t('harvester.virtualMachine.input.username')"
|
||||
class="mb-20"
|
||||
required
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
/>
|
||||
</template>
|
||||
</ModalWithCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.vm__credentials-users {
|
||||
.hasUsers.labeled-select.edit.taggable {
|
||||
.labeled-tooltip {
|
||||
.icon.status-icon {
|
||||
top: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import { Banner } from '@components/Banner';
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { removeObject } from '@shell/utils/array';
|
||||
import { ACCESS_CREDENTIALS } from '../../../config/harvester-map';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
|
||||
const _NEW = '_NEW';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Banner,
|
||||
InfoBox
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
resource: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create',
|
||||
},
|
||||
|
||||
disableCreate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
isQemuInstalled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
ACCESS_CREDENTIALS,
|
||||
toRemove: [],
|
||||
toSave: [],
|
||||
rows: [],
|
||||
newUserOptions: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({ t: 'i18n/t' }),
|
||||
|
||||
isEdit() {
|
||||
return this.mode === _EDIT;
|
||||
},
|
||||
|
||||
historyUsersOptions() {
|
||||
const out = JSON.parse(this.resource?.spec?.template?.metadata?.annotations?.[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_USERS] || '[]');
|
||||
|
||||
out.unshift({
|
||||
label: this.t('harvester.virtualMachine.accessCredentials.injectSSH.addUser'),
|
||||
value: _NEW,
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
userOptions() {
|
||||
return Array.from(new Set([...this.historyUsersOptions, ...this.newUserOptions]));
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
this.rows = [...neu];
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
addCredentials(type) {
|
||||
const neu = {
|
||||
source: type,
|
||||
users: [],
|
||||
sshkeys: [],
|
||||
username: '',
|
||||
newPassword: '',
|
||||
secretName: this.generateSecretName(this.resource.metadata.name),
|
||||
};
|
||||
|
||||
this.rows.push(neu);
|
||||
this.update();
|
||||
},
|
||||
|
||||
remove(row) {
|
||||
removeObject(this.rows, row);
|
||||
this.update();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.$emit('input', this.rows);
|
||||
},
|
||||
|
||||
updateNewUser(newUser) {
|
||||
this.newUserOptions.push(newUser);
|
||||
},
|
||||
|
||||
generateSecretName(name) {
|
||||
return name ? `${ name }-${ randomStr(5).toLowerCase() }` : undefined;
|
||||
},
|
||||
|
||||
componentFor(type) {
|
||||
switch (type) {
|
||||
case ACCESS_CREDENTIALS.RESET_PWD:
|
||||
return require(`./type/basicAuth.vue`).default;
|
||||
case ACCESS_CREDENTIALS.INJECT_SSH:
|
||||
return require(`./type/sshkey.vue`).default;
|
||||
}
|
||||
},
|
||||
|
||||
headerFor(type) {
|
||||
return {
|
||||
userPassword: this.$store.getters['i18n/t']('harvester.virtualMachine.accessCredentials.resetPwd.label'),
|
||||
sshPublicKey: this.$store.getters['i18n/t']('harvester.virtualMachine.accessCredentials.injectSSH.label'),
|
||||
}[type];
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Banner class="mb-20" color="info" :label="t('harvester.virtualMachine.accessCredentials.tips')" />
|
||||
|
||||
<div v-for="(row, i) in rows" :key="row.id">
|
||||
<InfoBox class="volume-source">
|
||||
<button v-if="isEdit" type="button" class="role-link btn btn-sm remove-vol" @click="remove(row)">
|
||||
<i class="icon icon-x" />
|
||||
</button>
|
||||
<h3>
|
||||
<span>
|
||||
{{ headerFor(row.source) }}
|
||||
</span>
|
||||
</h3>
|
||||
<div>
|
||||
<component
|
||||
:is="componentFor(row.source)"
|
||||
v-model="rows[i]"
|
||||
:rows="rows"
|
||||
:resource="resource"
|
||||
:user-options="userOptions"
|
||||
:mode="mode"
|
||||
:idx="i"
|
||||
@update="update"
|
||||
@update:newUser="updateNewUser"
|
||||
/>
|
||||
</div>
|
||||
</InfoBox>
|
||||
</div>
|
||||
|
||||
<div v-if="isEdit && isQemuInstalled" class="mt-20">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-primary mr-15 mb-10"
|
||||
@click="addCredentials(ACCESS_CREDENTIALS.RESET_PWD)"
|
||||
>
|
||||
{{ t('harvester.virtualMachine.accessCredentials.resetPwd.label') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-primary mr-15 mb-10"
|
||||
@click="addCredentials(ACCESS_CREDENTIALS.INJECT_SSH)"
|
||||
>
|
||||
{{ t('harvester.virtualMachine.accessCredentials.injectSSH.label') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.volume-source {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.remove-vol {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<script>
|
||||
import Password from '@shell/components/form/Password';
|
||||
import AccessCredentialsUsers from '../AccessCredentialsUsers';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditVolume',
|
||||
components: { Password, AccessCredentialsUsers },
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
|
||||
resource: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
userOptions: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
defaultPwdValue: {
|
||||
username: '',
|
||||
newPassword: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.$emit('update');
|
||||
},
|
||||
|
||||
updateUser(neu) {
|
||||
this.$set(this.value, 'username', neu);
|
||||
this.update();
|
||||
},
|
||||
|
||||
updateNewUser(neu) {
|
||||
this.$emit('update:newUser', neu);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" @input="update">
|
||||
<div class="col span-6">
|
||||
<AccessCredentialsUsers
|
||||
v-model="value.username"
|
||||
:resource="resource"
|
||||
:user-options="userOptions"
|
||||
:multiple="false"
|
||||
:mode="mode"
|
||||
@update:user="updateUser"
|
||||
@update:newUser="updateNewUser"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<Password
|
||||
ref="password"
|
||||
v-model="value.newPassword"
|
||||
:mode="mode"
|
||||
:disabled="mode !== 'edit'"
|
||||
:label="t('harvester.virtualMachine.input.password')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
</style>
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<script>
|
||||
import SSHKey from '../../VirtualMachineSSHKey';
|
||||
import AccessCredentialsUsers from '../AccessCredentialsUsers';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditVolume',
|
||||
components: { SSHKey, AccessCredentialsUsers },
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
|
||||
resource: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
userOptions: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.$emit('update');
|
||||
},
|
||||
|
||||
updateUser(neu) {
|
||||
this.$set(this.value, 'users', neu);
|
||||
this.update();
|
||||
},
|
||||
|
||||
updateSSH(neu) {
|
||||
this.$set(this.value, 'sshkeys', neu);
|
||||
this.update();
|
||||
},
|
||||
|
||||
updateNewUser(neu) {
|
||||
this.$emit('update:newUser', neu);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="columns row">
|
||||
<div class="col span-6">
|
||||
<AccessCredentialsUsers
|
||||
v-model="value.users"
|
||||
:resource="resource"
|
||||
:user-options="userOptions"
|
||||
:mode="mode"
|
||||
:multiple="true"
|
||||
@update:user="updateUser"
|
||||
@update:newUser="updateNewUser"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<SSHKey
|
||||
v-model="value.sshkeys"
|
||||
class="mb-20"
|
||||
:namespace="resource.metadata.namespace"
|
||||
:mode="mode"
|
||||
:searchable="false"
|
||||
@update:sshKey="updateSSH"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user