315 lines
13 KiB
Vue
315 lines
13 KiB
Vue
<script>
|
|
|
|
import { Button, ButtonGroup, Checkbox, Dialog, Dropdown, FormGroup, InputDialog, NumberInput, PasswordInput, TableView, TextInput } from 'pankow';
|
|
|
|
import Section from './Section.vue';
|
|
|
|
import { createVolumesModel } from '../models/VolumesModel.js';
|
|
|
|
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
|
const accessToken = localStorage.token;
|
|
|
|
const volumesModel = createVolumesModel(API_ORIGIN, accessToken);
|
|
|
|
export default {
|
|
name: 'VolumesView',
|
|
components: {
|
|
Button,
|
|
ButtonGroup,
|
|
Section,
|
|
Checkbox,
|
|
Dialog,
|
|
Dropdown,
|
|
FormGroup,
|
|
InputDialog,
|
|
NumberInput,
|
|
PasswordInput,
|
|
TableView,
|
|
TextInput,
|
|
},
|
|
computed: {
|
|
volumeDialogValid() {
|
|
const data = this.volumeDialogData;
|
|
|
|
if (data.mode === 'new') {
|
|
if (!data.name) return false;
|
|
if (!data.mountType) return false;
|
|
}
|
|
|
|
switch (data.mountType) {
|
|
case 'filesystem':
|
|
case 'mountpoint':
|
|
if (!data.hostPath) return false;
|
|
if (!data.hostPath) return false;
|
|
break;
|
|
case 'ext4':
|
|
case 'xfs':
|
|
if (!data.diskPath) return false;
|
|
break;
|
|
case 'nfs':
|
|
if (!data.host) return false;
|
|
if (!data.remoteDirectory) return false;
|
|
break;
|
|
case 'sshfs':
|
|
if (!data.host) return false;
|
|
if (!data.remoteDirectory) return false;
|
|
if (!data.post) return false;
|
|
if (!data.user) return false;
|
|
if (!data.privateKey) return false;
|
|
break;
|
|
case 'cifs':
|
|
if (!data.host) return false;
|
|
if (!data.remoteDirectory) return false;
|
|
if (!data.username) return false;
|
|
if (!data.password) return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
busy: true,
|
|
mountTypeOptions: [
|
|
{ name: 'CIFS', value: 'cifs' },
|
|
{ name: 'EXT4', value: 'ext4' },
|
|
{ name: 'Filesystem', value: 'filesystem' },
|
|
{ name: 'Filesystem (Mountpoint)', value: 'mountpoint' },
|
|
{ name: 'NFS', value: 'nfs' },
|
|
{ name: 'SSHFS', value: 'sshfs' },
|
|
{ name: 'XFS', value: 'xfs' },
|
|
],
|
|
columns: {
|
|
status: {},
|
|
name: { label: 'Name', sort: true },
|
|
mountType: { label: 'Type', sort: true },
|
|
target: { label: 'Target', sort: true },
|
|
actions: {}
|
|
},
|
|
volumes: [],
|
|
volumeDialogData: {
|
|
error: null,
|
|
busy: false,
|
|
mode: '', // edit or new
|
|
name: '',
|
|
// dynamic extra props from openVolumeDialog
|
|
},
|
|
};
|
|
},
|
|
methods: {
|
|
async refresh() {
|
|
this.busy = true;
|
|
this.volumes = await volumesModel.list();
|
|
this.busy = false;
|
|
|
|
for (const v of this.volumes) {
|
|
const status = await volumesModel.getStatus(v.id);
|
|
v.state = status.state;
|
|
v.message = status.message;
|
|
}
|
|
},
|
|
async openVolumeDialog(volume) {
|
|
this.volumeDialogData.error = null;
|
|
this.volumeDialogData.mode = volume ? 'edit' : 'new';
|
|
this.volumeDialogData.id = volume ? volume.id : '';
|
|
this.volumeDialogData.name = volume ? volume.name : '';
|
|
this.volumeDialogData.mountType = volume ? volume.mountType : '';
|
|
this.volumeDialogData.host = volume ? volume.mountOptions.host : '';
|
|
this.volumeDialogData.seal = volume ? volume.mountOptions.seal : false;
|
|
this.volumeDialogData.port = volume ? volume.mountOptions.port : 0;
|
|
this.volumeDialogData.remoteDir = volume ? volume.mountOptions.remoteDir : '';
|
|
this.volumeDialogData.username = volume ? volume.mountOptions.username : '';
|
|
this.volumeDialogData.password = volume ? volume.mountOptions.password : '';
|
|
this.volumeDialogData.diskPath = volume ? volume.mountOptions.diskPath : '';
|
|
this.volumeDialogData.hostPath = volume ? volume.mountOptions.hostPath : '';
|
|
|
|
let blockDevices = await volumesModel.getBlockDevices();
|
|
|
|
// only offer unmounted disks
|
|
blockDevices = blockDevices.filter(d => !d.mountpoint);
|
|
|
|
// amend label for UI
|
|
blockDevices.forEach(d => d.label = d.path);
|
|
|
|
this.volumeDialogData.ext4BlockDevices = blockDevices.filter(d => d.type === 'ext4');
|
|
this.volumeDialogData.xfsBlockDevices = blockDevices.filter(d => d.type === 'xfs');
|
|
|
|
this.$refs.volumeDialog.open();
|
|
},
|
|
async submitVolumeDialog() {
|
|
this.volumeDialogData.busy = true;
|
|
|
|
const mountOptions = {
|
|
host: this.volumeDialogData.host,
|
|
seal: this.volumeDialogData.seal,
|
|
port: this.volumeDialogData.port,
|
|
remoteDir: this.volumeDialogData.remoteDir,
|
|
username: this.volumeDialogData.username,
|
|
password: this.volumeDialogData.password,
|
|
diskPath: this.volumeDialogData.diskPath,
|
|
hostPath: this.volumeDialogData.hostPath,
|
|
};
|
|
|
|
try {
|
|
if (this.volumeDialogData.mode === 'new') {
|
|
await volumesModel.add(this.volumeDialogData.name, this.volumeDialogData.mountType, mountOptions);
|
|
} else {
|
|
await volumesModel.update(this.volumeDialogData.id, mountOptions);
|
|
}
|
|
} catch (error) {
|
|
this.volumeDialogData.error = error.body ? error.body.message : 'Internal error';
|
|
this.volumeDialogData.busy = false;
|
|
console.error(error);
|
|
return;
|
|
}
|
|
|
|
await this.refresh();
|
|
|
|
this.$refs.volumeDialog.close();
|
|
this.volumeDialogData.busy = false;
|
|
},
|
|
async onRemove(volume) {
|
|
const yes = await this.$refs.inputDialog.confirm({
|
|
message: `Really remove volume ${volume.name}?`,
|
|
confirmStyle: 'danger',
|
|
confirmLabel: this.$t('volumes.removeVolumeDialog.removeAction'),
|
|
rejectLabel: this.$t('main.dialog.cancel')
|
|
});
|
|
|
|
if (!yes) return;
|
|
|
|
await volumesModel.remove(volume.id);
|
|
await this.refresh();
|
|
},
|
|
async remount(volume) {
|
|
await volumesModel.remount(volume.id);
|
|
|
|
const status = await volumesModel.getStatus(volume.id);
|
|
volume.state = status.state;
|
|
volume.message = status.message;
|
|
|
|
window.pankow.notify('Remount attempt finished');
|
|
}
|
|
},
|
|
async mounted() {
|
|
await this.refresh();
|
|
}
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="content">
|
|
<InputDialog ref="inputDialog" />
|
|
|
|
<Dialog ref="volumeDialog"
|
|
:title="volumeDialogData.mode === 'edit' ? $t('volumes.editVolumeDialog.title', { name: volumeDialogData.name }) : $t('volumes.addVolumeDialog.title')"
|
|
:reject-label="$t('main.dialog.cancel')"
|
|
:confirm-label="$t('main.dialog.save')"
|
|
confirm-style="success"
|
|
:confirm-active="volumeDialogValid"
|
|
:confirm-busy="volumeDialogData.busy"
|
|
@confirm="submitVolumeDialog()"
|
|
>
|
|
<form @submit="submitVolumeDialog()" autocomplete="off">
|
|
<fieldset :disabled="volumeDialogData.busy">
|
|
<input style="display: none;" type="submit" :disabled="!volumeDialogValid" />
|
|
|
|
<p class="has-error" v-show="volumeDialogData.error">{{ volumeDialogData.error }}</p>
|
|
|
|
<FormGroup v-if="volumeDialogData.mode === 'new'">
|
|
<label for="volumeName">{{ $t('volumes.name') }}</label>
|
|
<TextInput id="volumeName" v-model="volumeDialogData.name" />
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<label for="volumeMountType">{{ $t('volumes.mountType') }}</label>
|
|
<Dropdown id="volumeMountType" v-model="volumeDialogData.mountType" :options="mountTypeOptions" option-label="name" option-key="value" :disabled="volumeDialogData.mode === 'edit'"/>
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'filesystem' || volumeDialogData.mountType === 'mountpoint'">
|
|
<label for="volumeHostPath">{{ $t('volumes.localDirectory') }}</label>
|
|
<TextInput id="volumeHostPath" v-model="volumeDialogData.hostPath" :placeholder="volumeDialogData.mountType === 'filesystem' ? '/srv/shared' : '/mnt/data'" />
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'ext4' || volumeDialogData.mountType === 'xfs'">
|
|
<label for="volumeDiskPath">{{ $t('volumes.addVolumeDialog.diskPath') }}</label>
|
|
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'ext4'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.ext4BlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
|
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'xfs'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.xfsBlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
|
<label for="volumeHost">{{ $t('volumes.addVolumeDialog.server') }}</label>
|
|
<TextInput v-model="volumeDialogData.host" id="volumeHost"/>
|
|
</FormGroup>
|
|
|
|
<Checkbox v-if="volumeDialogData.mountType === 'cifs'" v-model="volumeDialogData.seal" :label="$t('backups.configureBackupStorage.cifsSealSupport')" />
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
|
<label for="volumePort">{{ $t('volumes.addVolumeDialog.port') }}</label>
|
|
<NumberInput v-model="volumeDialogData.port" id="volumePort" min="0"/>
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
|
<label for="volumeRemoteDir">{{ $t('volumes.addVolumeDialog.remoteDirectory') }}</label>
|
|
<TextInput v-model="volumeDialogData.remoteDir" id="volumeRemoteDir" placeholder="/share" />
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
|
<label for="volumeUsername">{{ $t('volumes.addVolumeDialog.username') }}</label>
|
|
<TextInput v-model="volumeDialogData.username" id="volumeUsername" />
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
|
<label for="volumePassword">{{ $t('volumes.addVolumeDialog.password') }}</label>
|
|
<PasswordInput v-model="volumeDialogData.password" id="volumePassword" />
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
|
<label for="volumeUser">{{ $t('volumes.addVolumeDialog.user') }}</label>
|
|
<TextInput v-model="volumeDialogData.user" id="volumeAddUser" />
|
|
</FormGroup>
|
|
|
|
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
|
<label for="volumePrivateKey">{{ $t('volumes.addVolumeDialog.privateKey') }}</label>
|
|
<textarea v-model="volumeDialogData.privateKey" id="volumePrivateKey"></textarea>
|
|
</FormGroup>
|
|
|
|
</fieldset>
|
|
</form>
|
|
</Dialog>
|
|
|
|
<Section :title="$t('volumes.title')">
|
|
<template #header-buttons>
|
|
<Button @click="openVolumeDialog()" icon="fa fa-plus">{{ $t('volumes.addVolumeAction') }}</Button>
|
|
</template>
|
|
|
|
<div v-html="$t('volumes.description')"></div>
|
|
<br/>
|
|
<TableView :columns="columns" :model="volumes" :busy="busy">
|
|
<template #target="slotProps">
|
|
{{ (slotProps.mountType === 'mountpoint' || slotProps.mountType === 'filesystem') ? slotProps.hostPath : (slotProps.mountOptions.host || slotProps.mountOptions.diskPath || slotProps.hostPath) + (slotProps.mountOptions.remoteDir || '') }}
|
|
</template>
|
|
<template #status="slotProps">
|
|
<div style="text-align: center;" v-tooltip="slotProps.message">
|
|
<i class="fa fa-circle" :style="{ color: slotProps.state === 'active' ? '#27CE65' : '#d9534f' }"></i>
|
|
</div>
|
|
</template>
|
|
<template #actions="slotProps">
|
|
<div class="table-actions">
|
|
<ButtonGroup>
|
|
<Button tool secondary outline small icon="fa fa-sync-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs' || slotProps.mountType === 'ext4' || slotProps.mountType === 'xfs'" v-tooltip="$t('volumes.remountActionTooltip')" @click="remount(slotProps)"></Button>
|
|
<Button tool secondary outline small icon="fa fa-pencil-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs'" v-tooltip="$t('volumes.editActionTooltip')" @click="openVolumeDialog(slotProps)"></Button>
|
|
<Button tool secondary outline small icon="fas fa-folder" v-tooltip="$t('volumes.openFileManagerActionTooltip')" :href="'/filemanager.html#/home/volume/' + slotProps.id" target="_blank"></Button>
|
|
</ButtonGroup>
|
|
<Button tool danger outline small icon="far fa-trash-alt" v-tooltip="$t('volumes.removeVolumeActionTooltip')" @click="onRemove(slotProps)"></Button>
|
|
</div>
|
|
</template>
|
|
</TableView>
|
|
</Section>
|
|
</div>
|
|
</template>
|