Yiya Chen f22644e058
fix: reset filesystem (#559)
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-10-17 12:15:58 +08:00

536 lines
14 KiB
Vue

<script>
import { allHash } from '@shell/utils/promise';
import {
CSI_DRIVER, LONGHORN, LONGHORN_DRIVER, LONGHORN_VERSION_V1, LONGHORN_VERSION_V2
} from '@shell/config/types';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabelValue from '@shell/components/LabelValue';
import { BadgeState } from '@components/BadgeState';
import { Banner } from '@components/Banner';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { RadioGroup } from '@components/Form/Radio';
import HarvesterDisk from '../../mixins/harvester-disk';
import Tags from '../../components/DiskTags';
import { HCI } from '../../types';
import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { LONGHORN_SYSTEM } from './index';
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
import ModalWithCard from '@shell/components/ModalWithCard';
import { randomStr } from '@shell/utils/string';
import { LONGHORN_V2_DATA_ENGINE } from './index.vue';
import { _EDIT } from '@shell/config/query-params';
const _NEW = '_NEW';
export default {
components: {
LabeledInput,
LabeledSelect,
LabelValue,
BadgeState,
Banner,
RadioGroup,
ModalWithCard,
Tags,
},
mixins: [
HarvesterDisk,
],
props: {
value: {
type: Object,
default: () => {
return {};
},
},
disks: {
type: Array,
default: () => [],
},
node: {
type: Object,
default: () => {
return {};
},
},
mode: {
type: String,
default: 'edit',
},
},
async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;
await allHash({
csiDrivers: this.$store.dispatch(`${ inStore }/findAll`, { type: CSI_DRIVER }),
lvmVolumeGroups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.LVM_VOLUME_GROUP }),
});
},
data() {
let provisioner = `${ this.value.provisioner || LONGHORN_DRIVER }`;
if (provisioner === LONGHORN_DRIVER) {
provisioner = `${ provisioner }_${ this.value.provisionerVersion || LONGHORN_VERSION_V1 }`;
}
return {
provisioner,
volumeGroupDialog: null,
randomStr: randomStr(10).toLowerCase(),
isOpen: false
};
},
computed: {
provisioners() {
const out = [];
const inStore = this.$store.getters['currentProduct'].inStore;
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
csiDrivers.forEach(({ name }) => {
switch (name) {
case LONGHORN_DRIVER:
out.push({
label: `harvester.host.disk.storage.longhorn.${ LONGHORN_VERSION_V1 }.label`,
value: `${ name }_${ LONGHORN_VERSION_V1 }`,
});
if (this.longhornSystemVersion === LONGHORN_VERSION_V2 || this.value.provisionerVersion === LONGHORN_VERSION_V2) {
out.push({
label: `harvester.host.disk.storage.longhorn.${ LONGHORN_VERSION_V2 }.label`,
value: `${ name }_${ LONGHORN_VERSION_V2 }`,
disabled: this.forceLonghornV1
});
}
break;
case LVM_DRIVER:
out.push({
label: 'harvester.host.disk.storage.lvm.label',
value: name,
});
break;
}
});
return out;
},
lvmVolumeGroups() {
const inStore = this.$store.getters['currentProduct'].inStore;
const lvmVolumeGroups = this.$store.getters[`${ inStore }/all`](HCI.LVM_VOLUME_GROUP) || [];
const out = lvmVolumeGroups.filter((group) => group.spec.nodeName === this.node.name).map((g) => g.spec.vgName);
out.unshift({
label: this.t('harvester.host.disk.lvmVolumeGroup.create'),
value: _NEW,
});
return out;
},
targetDisk() {
return this.disks.find((disk) => disk.name === this.value.name);
},
schedulableTooltipMessage() {
const { name, path } = this.value;
if (this.targetDisk && !this.targetDisk.allowScheduling && name && path) {
return this.t('harvester.host.disk.allowScheduling.tooltip', { name, path });
} else {
return this.schedulableCondition.message;
}
},
allowSchedulingOptions() {
return [{
label: this.t('generic.enabled'),
value: true,
}, {
label: this.t('generic.disabled'),
value: false,
}];
},
evictionRequestedOptions() {
return [{
label: this.t('generic.yes'),
value: true,
}, {
label: this.t('generic.no'),
value: false,
}];
},
mountedMessage() {
const state = this.blockDevice?.metadata?.state || {};
if (state?.error) {
return state?.message;
} else {
return '';
}
},
isProvisioned() {
return this.blockDevice?.isProvisioned;
},
forceFormattedDisabled() {
const fileSystem = this.blockDevice?.status?.deviceStatus?.fileSystem.type;
const systems = ['ext4', 'XFS'];
if (this.blockDevice?.childParts?.length > 0) {
return true;
} else if (systems.includes(fileSystem)) {
return false;
} else if (!fileSystem) {
return true;
} else {
return !this.canEditPath;
}
},
canEditPath() {
if (this.mountedMessage) {
return true;
}
if (this.value.isNew && !this.value.originPath) {
return true;
}
return false;
},
isFormatted() {
return !!this.blockDevice?.status?.deviceStatus?.fileSystem?.LastFormattedAt;
},
formattedBannerLabel() {
const system = this.blockDevice?.status?.deviceStatus?.fileSystem?.type;
const label = this.t('harvester.host.disk.lastFormattedAt.info');
if (system) {
return `${ label } ${ this.t('harvester.host.disk.fileSystem.info', { system }) }`;
} else {
return label;
}
},
provisionPhase() {
return this.blockDevice?.provisionPhase || {};
},
blockDevice() {
const inStore = this.$store.getters['currentProduct'].inStore;
const name = this.value?.name;
return this.$store.getters[`${ inStore }/byId`](HCI.BLOCK_DEVICE, `${ LONGHORN_SYSTEM }/${ name }`) || {};
},
isCorrupted() {
return this.blockDevice?.status?.deviceStatus?.fileSystem?.corrupted;
},
isFormatting() {
return this.blockDevice.isFormatting;
},
longhornSystemVersion() {
const inStore = this.$store.getters['currentProduct'].inStore;
const v2DataEngine = this.$store.getters[`${ inStore }/byId`](LONGHORN.SETTINGS, LONGHORN_V2_DATA_ENGINE) || {};
return v2DataEngine.value === 'true' ? LONGHORN_VERSION_V2 : LONGHORN_VERSION_V1;
},
forceLonghornV1() {
return this.node?.labels[HCI_LABELS_ANNOTATIONS.DISABLE_LONGHORN_V2_ENGINE] === 'true';
},
isLvm() {
return this.value.provisioner === LVM_DRIVER;
},
isLonghorn() {
return this.value.provisioner === LONGHORN_DRIVER;
},
isLonghornV1() {
return this.isLonghorn && this.value.provisionerVersion === LONGHORN_VERSION_V1;
},
provisionerTooltip() {
if (
this.mode === _EDIT &&
this.isLonghorn &&
this.longhornSystemVersion === LONGHORN_VERSION_V2 &&
this.forceLonghornV1
) {
return this.t('harvester.storage.storageClass.longhorn.versionTooltip');
}
return null;
}
},
watch: {
provisioner(value) {
this.randomStr = randomStr(10).toLowerCase();
const [provisioner, provisionerVersion] = value?.split('_');
this.value.provisioner = provisioner;
if (provisioner === LONGHORN_DRIVER) {
this.value.provisionerVersion = provisionerVersion || LONGHORN_VERSION_V1;
} else {
this.value.provisionerVersion = undefined;
}
},
'value.lvmVolumeGroup'(neu) {
if (neu === _NEW) {
this.value.lvmVolumeGroup = null;
this.showCreateVolumeGroup();
}
}
},
methods: {
showCreateVolumeGroup() {
this.volumeGroupDialog = null;
this.isOpen = true;
},
hideCreateVolumeGroup() {
this.isOpen = false;
},
saveCreateVolumeGroup(buttonCb) {
buttonCb(true);
this.value.lvmVolumeGroup = this.volumeGroupDialog;
this.hideCreateVolumeGroup();
},
update() {
this.$emit('update:value', this.value);
},
},
};
</script>
<template>
<div
class="disk"
@update:value="update"
>
<div class="mt-10" />
<Banner
v-if="mountedMessage && isProvisioned"
color="error"
:label="mountedMessage"
/>
<Banner
v-if="isFormatting"
color="info"
:label="t('harvester.host.disk.fileSystem.formatting')"
/>
<Banner
v-else-if="isFormatted && isLonghornV1 && !isCorrupted"
color="info"
:label="formattedBannerLabel"
/>
<div v-if="!value.isNew">
<div class="row">
<div class="col span-12">
<Tags
v-model:value="value.tags"
:label="t('harvester.host.disk.tags.label')"
:add-label="t('harvester.host.disk.tags.addLabel')"
:mode="mode"
/>
</div>
</div>
<div class="row mt-10">
<div class="col span-12">
<div class="pull-left">
<RadioGroup
v-model:value="value.allowScheduling"
name="diskScheduling"
:label="t('harvester.host.disk.allowScheduling.label')"
:mode="mode"
:options="allowSchedulingOptions"
:row="true"
/>
</div>
<div class="pull-right">
{{ t('harvester.host.disk.conditions') }}:
<BadgeState
v-clean-tooltip="readyCondition.message"
:color="readyCondition.status === 'True' ? 'bg-success' : 'bg-error' "
:icon="readyCondition.status === 'True' ? 'icon-checkmark' : 'icon-warning' "
label="Ready"
class="mr-10 ml-10 state"
/>
<BadgeState
v-clean-tooltip="schedulableTooltipMessage"
:color="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'bg-success' : 'bg-error' "
:icon="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'icon-checkmark' : 'icon-warning' "
label="Schedulable"
class="mr-10 state"
/>
<BadgeState
v-if="provisionPhase.label"
:color="provisionPhase.color"
:icon="provisionPhase.icon"
:label="provisionPhase.label"
class="mr-10 state"
/>
</div>
</div>
</div>
<div
v-if="!value.isNew"
class="row mt-30"
>
<div class="col flex span-12">
<LabelValue
:name="t('harvester.host.disk.storageAvailable.label')"
:value="value.storageAvailable"
/>
<LabelValue
:name="t('harvester.host.disk.storageScheduled.label')"
:value="value.storageScheduled"
/>
<LabelValue
:name="t('harvester.host.disk.storageMaximum.label')"
:value="value.storageMaximum"
/>
</div>
</div>
<hr class="mt-10" />
</div>
<div class="row mt-10">
<div class="col span-12">
<LabeledInput
v-model:value="value.displayName"
:label="t('generic.name')"
:disabled="true"
/>
</div>
</div>
<div class="row mt-10">
<div :class="`col span-${ value.isNew ? '6': '12' }`">
<LabeledSelect
v-model:value="provisioner"
:mode="mode"
label-key="harvester.host.disk.provisioner"
:localized-label="true"
:searchable="true"
:options="provisioners"
:disabled="isProvisioned || !value.isNew"
:tooltip="provisionerTooltip"
@keydown.native.enter.prevent="()=>{}"
/>
</div>
<div
v-if="(value.isNew && isLonghornV1) || isCorrupted"
class="col span-6"
>
<RadioGroup
v-model:value="value.forceFormatted"
:mode="mode"
name="forceFormatted"
label-key="harvester.host.disk.forceFormatted.label"
:labels="[t('generic.no'),t('harvester.host.disk.forceFormatted.yes')]"
:options="[false, true]"
:disabled="forceFormattedDisabled"
tooltip-key="harvester.host.disk.forceFormatted.toolTip"
>
</RadioGroup>
</div>
<div
v-if="value.isNew && isLvm"
class="col span-6"
>
<LabeledSelect
v-model:value="value.lvmVolumeGroup"
:mode="mode"
label-key="harvester.host.disk.lvmVolumeGroup.label"
:localized-label="true"
:searchable="false"
:taggable="true"
:multiple="false"
:required="true"
:disabled="isProvisioned"
:options="lvmVolumeGroups"
@keydown.native.enter.prevent="()=>{}"
/>
</div>
</div>
<ModalWithCard
v-if="isOpen"
:ref="randomStr"
:name="randomStr"
width="30%"
@finish="saveCreateVolumeGroup"
@close="hideCreateVolumeGroup"
>
<template #title>
{{ t('harvester.host.disk.lvmVolumeGroup.label') }}
</template>
<template #content>
<LabeledInput
v-model:value="volumeGroupDialog"
:label="t('generic.name')"
class="mb-20"
required
@keydown.native.enter.prevent="()=>{}"
/>
</template>
</ModalWithCard>
</div>
</template>
<style lang="scss" scoped>
.close {
top: 10px;
right: 10px;
padding:0;
position: absolute;
}
.disk {
position: relative;
.secret-name {
height: $input-height;
}
&:not(:last-of-type) {
padding-bottom: 10px;
margin-bottom: 30px;
}
}
.flex {
display: flex;
justify-content: space-between;
}
.badge-state {
padding: 2px 5px;
}
</style>