mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-04 05:06:05 +08:00
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import ResourceDetail from '@shell/components/ResourceDetail/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterResourcedId',
|
||||
components: { ResourceDetail },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResourceDetail />
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import ResourceDetail from '@shell/components/ResourceDetail/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterResourcedNamespaceId',
|
||||
components: { ResourceDetail },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResourceDetail />
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import ResourceDetail from '@shell/components/ResourceDetail/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterResourceCreate',
|
||||
components: { ResourceDetail },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResourceDetail />
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import ResourceList from '@shell/components/ResourceList/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterResourcedList',
|
||||
components: { ResourceList },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResourceList />
|
||||
</template>
|
||||
@@ -0,0 +1,328 @@
|
||||
<script>
|
||||
import CruResource from '@shell/components/CruResource';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import UpgradeInfo from '../../../../components/UpgradeInfo';
|
||||
|
||||
import { HCI } from '../../../../types';
|
||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../../config/harvester';
|
||||
|
||||
const IMAGE_METHOD = {
|
||||
NEW: 'new',
|
||||
EXIST: 'exist'
|
||||
};
|
||||
|
||||
const DOWNLOAD = 'download';
|
||||
const UPLOAD = 'upload';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterAirgapUpgrade',
|
||||
components: {
|
||||
Checkbox, CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
await this.$store.dispatch('harvester/findAll', { type: HCI.IMAGE });
|
||||
|
||||
const value = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.UPGRADE,
|
||||
metadata: {
|
||||
generateName: 'hvst-upgrade-',
|
||||
namespace: 'harvester-system',
|
||||
},
|
||||
spec: { image: '' }
|
||||
});
|
||||
|
||||
const imageValue = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.IMAGE,
|
||||
metadata: {
|
||||
name: '',
|
||||
namespace: 'harvester-system',
|
||||
generateName: 'image-',
|
||||
annotations: {}
|
||||
},
|
||||
spec: {
|
||||
sourceType: UPLOAD,
|
||||
displayName: '',
|
||||
checksum: ''
|
||||
},
|
||||
});
|
||||
|
||||
this.value = value;
|
||||
this.imageValue = imageValue;
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: null,
|
||||
file: {},
|
||||
imageId: '',
|
||||
imageSource: IMAGE_METHOD.NEW,
|
||||
sourceType: UPLOAD,
|
||||
imageValue: null,
|
||||
errors: [],
|
||||
enableLogging: true,
|
||||
IMAGE_METHOD
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
doneRoute() {
|
||||
return `${ HARVESTER_PRODUCT }-c-cluster-resource`;
|
||||
},
|
||||
|
||||
osImageOptions() {
|
||||
return this.$store.getters['harvester/all'](HCI.IMAGE)
|
||||
.filter(I => I.isOSImage)
|
||||
.map((I) => {
|
||||
return {
|
||||
label: I.spec.displayName,
|
||||
value: I.id,
|
||||
disabled: !I.isReady
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
uploadImage() {
|
||||
return this.imageSource === IMAGE_METHOD.NEW;
|
||||
},
|
||||
|
||||
fileName() {
|
||||
return this.file?.name || '';
|
||||
},
|
||||
|
||||
canEnableLogging() {
|
||||
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
done() {
|
||||
this.$router.push({
|
||||
name: this.doneRoute,
|
||||
params: { resource: HCI.SETTING, product: 'harvester' }
|
||||
});
|
||||
},
|
||||
|
||||
async save(buttonCb) {
|
||||
let res = null;
|
||||
|
||||
this.errors = [];
|
||||
if (!this.imageValue.spec.displayName && this.uploadImage) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('generic.name') }));
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.imageSource === IMAGE_METHOD.NEW) {
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
|
||||
|
||||
if (this.sourceType === UPLOAD) {
|
||||
this.imageValue.spec.sourceType = UPLOAD;
|
||||
const file = this.file;
|
||||
|
||||
if (!file.name) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.selectExitImage'));
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.imageValue.spec.url = '';
|
||||
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = file.name;
|
||||
|
||||
res = await this.imageValue.save();
|
||||
|
||||
res.uploadImage(file);
|
||||
} else if (this.sourceType === DOWNLOAD) {
|
||||
this.imageValue.spec.sourceType = DOWNLOAD;
|
||||
if (!this.imageValue.spec.url) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl'));
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res = await this.imageValue.save();
|
||||
}
|
||||
|
||||
this.value.spec.image = res.id;
|
||||
} else if (this.imageSource === IMAGE_METHOD.EXIST) {
|
||||
if (!this.imageId) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.chooseFile'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.value.spec.image = this.imageId;
|
||||
}
|
||||
|
||||
if (this.canEnableLogging) {
|
||||
this.value.spec.logEnabled = this.enableLogging;
|
||||
}
|
||||
|
||||
await this.value.save();
|
||||
this.done();
|
||||
buttonCb(true);
|
||||
} catch (e) {
|
||||
this.errors = exceptionToErrorsArray(e);
|
||||
buttonCb(false);
|
||||
}
|
||||
},
|
||||
|
||||
handleFileUpload() {
|
||||
this.file = this.$refs.file.files[0];
|
||||
},
|
||||
|
||||
selectFile() {
|
||||
this.$refs.file.value = null;
|
||||
this.$refs.file.click();
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'imageValue.spec.url': {
|
||||
handler(neu) {
|
||||
const suffixName = neu?.split('/')?.pop();
|
||||
const splitName = suffixName?.split('.') || [];
|
||||
const fileSuffix = splitName?.pop()?.toLowerCase();
|
||||
|
||||
if (splitName.length > 1 && fileSuffix === 'iso' && !this.imageValue.spec.displayName) {
|
||||
this.imageValue.spec.displayName = suffixName;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
file(neu) {
|
||||
if (!this.imageValue.spec.displayName && neu.name) {
|
||||
this.imageValue.spec.displayName = neu.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="value" id="air-gap">
|
||||
<h3 class="mb-20">
|
||||
{{ t('harvester.upgradePage.osUpgrade') }}
|
||||
</h3>
|
||||
<CruResource
|
||||
:done-route="doneRoute"
|
||||
:resource="value"
|
||||
mode="create"
|
||||
:errors="errors"
|
||||
:can-yaml="false"
|
||||
finish-button-mode="upgrade"
|
||||
:cancel-event="true"
|
||||
@finish="save"
|
||||
@cancel="done"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model="imageSource"
|
||||
class="image-group"
|
||||
name="image"
|
||||
:options="[
|
||||
IMAGE_METHOD.NEW,
|
||||
IMAGE_METHOD.EXIST,
|
||||
]"
|
||||
:labels="[
|
||||
t('harvester.upgradePage.uploadNew'),
|
||||
t('harvester.upgradePage.selectExisting'),
|
||||
]"
|
||||
/>
|
||||
|
||||
<UpgradeInfo />
|
||||
|
||||
<div v-if="uploadImage">
|
||||
<LabeledInput v-model.trim="imageValue.spec.displayName" class="mb-20" label-key="harvester.fields.name" required />
|
||||
|
||||
<LabeledInput v-model="imageValue.spec.checksum" class="mb-10" label-key="harvester.setting.upgrade.checksum" />
|
||||
|
||||
<Checkbox
|
||||
v-if="canEnableLogging"
|
||||
v-model="enableLogging"
|
||||
class="check mb-20"
|
||||
type="checkbox"
|
||||
:label="t('harvester.upgradePage.enableLogging')"
|
||||
/>
|
||||
|
||||
<RadioGroup
|
||||
v-model="sourceType"
|
||||
class="mb-20 image-group"
|
||||
name="sourceType"
|
||||
:options="[
|
||||
'upload',
|
||||
'download',
|
||||
]"
|
||||
:labels="[
|
||||
t('harvester.image.sourceType.upload'),
|
||||
t('harvester.image.sourceType.download')
|
||||
]"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-if="sourceType === 'download'"
|
||||
v-model.trim="imageValue.spec.url"
|
||||
class="labeled-input--tooltip"
|
||||
required
|
||||
label-key="harvester.image.url"
|
||||
/>
|
||||
|
||||
<div v-else class="chooseFile">
|
||||
<button
|
||||
type="button"
|
||||
class="btn role-primary"
|
||||
@click="selectFile"
|
||||
>
|
||||
{{ t('harvester.image.uploadFile') }}
|
||||
<input
|
||||
v-show="false"
|
||||
id="file"
|
||||
ref="file"
|
||||
type="file"
|
||||
accept=".iso"
|
||||
@change="handleFileUpload()"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<span :class="{ 'text-muted': !fileName }" class="ml-20">
|
||||
{{ fileName ? fileName : t('harvester.generic.noFileChosen') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LabeledSelect
|
||||
v-else
|
||||
v-model="imageId"
|
||||
:options="osImageOptions"
|
||||
required
|
||||
class="mb-20"
|
||||
label-key="harvester.fields.image"
|
||||
/>
|
||||
</CruResource>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#air-gap {
|
||||
::v-deep .image-group .radio-group {
|
||||
display: flex;
|
||||
.radio-container {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
.chooseFile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import ResourceDetail from '@shell/components/ResourceDetail';
|
||||
|
||||
export default {
|
||||
name: 'AlertmanagerConfig',
|
||||
components: { ResourceDetail },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResourceDetail />
|
||||
</template>
|
||||
@@ -0,0 +1,304 @@
|
||||
<script>
|
||||
import ActionMenu from '@shell/components/ActionMenu';
|
||||
import ReceiverConfig from '@shell/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig';
|
||||
import ButtonGroup from '@shell/components/ButtonGroup';
|
||||
import ResourceYaml from '@shell/components/ResourceYaml';
|
||||
import { createYaml } from '@shell/utils/create-yaml';
|
||||
import { EDITOR_MODES } from '@shell/components/YamlEditor';
|
||||
import { MONITORING, SCHEMA } from '@shell/config/types';
|
||||
import {
|
||||
_CREATE, _EDIT, _VIEW, _CONFIG, _YAML, _DETAIL
|
||||
} from '@shell/config/query-params';
|
||||
|
||||
import { clone } from '@shell/utils/object';
|
||||
|
||||
export default {
|
||||
name: 'AlertmanagerConfigReceiverCreateEdit',
|
||||
components: {
|
||||
ActionMenu,
|
||||
ButtonGroup,
|
||||
ReceiverConfig,
|
||||
ResourceYaml
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
this.receiverName = this.$route.query.receiverName;
|
||||
|
||||
const alertmanagerConfigId = this.$route.params.alertmanagerconfigid;
|
||||
const originalAlertmanagerConfigResource = await this.$store.dispatch(`${ inStore }/find`, { type: MONITORING.ALERTMANAGERCONFIG, id: alertmanagerConfigId });
|
||||
const alertmanagerConfigResource = await this.$store.dispatch(`${ inStore }/clone`, { resource: originalAlertmanagerConfigResource });
|
||||
const mode = this.$route.query.mode;
|
||||
|
||||
if (mode !== _CREATE) {
|
||||
const existingReceiverData = (alertmanagerConfigResource.spec.receivers || []).find((receiverData) => {
|
||||
return receiverData.name === this.receiverName;
|
||||
});
|
||||
|
||||
if (existingReceiverData) {
|
||||
this.receiverValue = existingReceiverData;
|
||||
}
|
||||
}
|
||||
|
||||
this.alertmanagerConfigId = alertmanagerConfigResource.id;
|
||||
this.alertmanagerConfigResource = alertmanagerConfigResource;
|
||||
this.alertmanagerConfigDetailRoute = alertmanagerConfigResource._detailLocation;
|
||||
},
|
||||
|
||||
// take edit link and edit request from AlertmanagerConfig resource
|
||||
// and pass it to ReceiverConfig as a prop
|
||||
|
||||
data() {
|
||||
return {
|
||||
actionMenuTargetElement: null,
|
||||
actionMenuTargetEvent: null,
|
||||
alertmanagerConfigId: '',
|
||||
alertmanagerConfigResource: null,
|
||||
alertmanagerConfigDetailRoute: null,
|
||||
config: _CONFIG,
|
||||
create: _CREATE,
|
||||
detail: _DETAIL,
|
||||
edit: _EDIT,
|
||||
receiverActionMenuIsOpen: false,
|
||||
receiverName: '',
|
||||
receiverValue: {},
|
||||
showPreview: false,
|
||||
view: _VIEW,
|
||||
viewOptions: [
|
||||
{
|
||||
labelKey: 'resourceDetail.masthead.config',
|
||||
value: 'config',
|
||||
}, {
|
||||
labelKey: 'resourceDetail.masthead.yaml',
|
||||
value: _YAML,
|
||||
}
|
||||
],
|
||||
yaml: _YAML
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentView() {
|
||||
return this.$route.query.currentView;
|
||||
},
|
||||
receiverActions() {
|
||||
const alertmanagerConfigActions = this.alertmanagerConfigResource?.availableActions;
|
||||
|
||||
if (!alertmanagerConfigActions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Receivers are not a separate resource, so they
|
||||
// should only have a subset of the AlertmanagerConfig
|
||||
// actions. So we take AlertmanagerConfig's actions and filter
|
||||
// out any that don't apply.
|
||||
// Example action data:
|
||||
// {
|
||||
// "action": "goToEdit",
|
||||
// "label": "Edit Config",
|
||||
// "icon": "icon icon-edit",
|
||||
// "enabled": true
|
||||
// },
|
||||
return this.alertmanagerConfigResource.getReceiverActions(alertmanagerConfigActions);
|
||||
},
|
||||
resourceYaml() {
|
||||
const resource = this.alertmanagerConfigResource;
|
||||
|
||||
const inStore = this.$store.getters['currentStore'](resource);
|
||||
const schemas = this.$store.getters[`${ inStore }/all`](SCHEMA);
|
||||
const clonedResource = clone(resource);
|
||||
|
||||
const out = createYaml(schemas, MONITORING.ALERTMANAGERCONFIG, clonedResource);
|
||||
|
||||
return out;
|
||||
},
|
||||
mode() {
|
||||
// Use the route as a dependency of the
|
||||
// computed property so that the component
|
||||
// updates when you navigate between edit
|
||||
// and view.
|
||||
return this.$route.query.mode;
|
||||
},
|
||||
editorMode() {
|
||||
if ( this.mode === this.view ) {
|
||||
return EDITOR_MODES.VIEW_CODE;
|
||||
} else if ( this.showPreview ) {
|
||||
return EDITOR_MODES.DIFF_CODE;
|
||||
}
|
||||
|
||||
return EDITOR_MODES.EDIT_CODE;
|
||||
},
|
||||
heading() {
|
||||
switch (this.$route.query.mode) {
|
||||
case this.create:
|
||||
return this.t('monitoring.alertmanagerConfig.receiverFormNames.create');
|
||||
case this.edit:
|
||||
if (this.currentView === this.yaml || this.$route.query.as === this.yaml) {
|
||||
// When you edit as YAML, you edit the whole AlertmanagerConfig
|
||||
// at once, so the header is just "Edit AlertmanagerConfig"
|
||||
return this.t('monitoring.alertmanagerConfig.receiverFormNames.editYaml');
|
||||
}
|
||||
|
||||
// When you edit as a form, you edit only the receiver,
|
||||
// so the form header is "Edit Receiver in AlertmanagerConfig"
|
||||
return this.t('monitoring.alertmanagerConfig.receiverFormNames.edit');
|
||||
|
||||
default:
|
||||
return this.t('monitoring.alertmanagerConfig.receiverFormNames.detail');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
// When creating or editing a receiver, in both cases
|
||||
// it is actually the one existing AlertmanagerConfig
|
||||
// being saved. Therefore we take the save from the
|
||||
// AlertmanagerConfig resource and pass it into the
|
||||
// receiver config form.
|
||||
saveOverride(buttonDone) {
|
||||
if (this.alertmanagerConfigResource.yamlError) {
|
||||
this.alertmanagerConfigResource.errors = this.alertmanagerConfigResource.errors || [];
|
||||
this.alertmanagerConfigResource.errors.push(this.alertmanagerConfigResource.yamlError);
|
||||
|
||||
buttonDone(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.alertmanagerConfigResource.save(...arguments);
|
||||
this.redirectToAlertmanagerConfigDetail();
|
||||
},
|
||||
handleButtonGroupClick(event) {
|
||||
if (event === this.yaml) {
|
||||
this.goToEditYaml(this.view);
|
||||
}
|
||||
if (event === this.config) {
|
||||
this.goToEdit(this.view);
|
||||
}
|
||||
},
|
||||
toggleReceiverActionMenu() {
|
||||
this.receiverActionMenuIsOpen = !this.receiverActionMenuIsOpen;
|
||||
},
|
||||
handleReceiverActionMenuClick(event) {
|
||||
this.actionMenuTargetElement = this.$refs.actions;
|
||||
this.actionMenuTargetEvent = event;
|
||||
this.toggleReceiverActionMenu();
|
||||
},
|
||||
goToEdit() {
|
||||
// 'goToEdit' is the exact name of an action for AlertmanagerConfig
|
||||
// and this method executes the action.
|
||||
this.$router.push(this.alertmanagerConfigResource.getEditReceiverConfigRoute(this.receiverValue.name, _EDIT));
|
||||
},
|
||||
goToEditYaml() {
|
||||
// 'goToEditYaml' is the exact name of an action for AlertmanagerConfig
|
||||
// and this method executes the action.
|
||||
this.$router.push(this.alertmanagerConfigResource.getEditReceiverYamlRoute(this.receiverValue.name, _EDIT));
|
||||
},
|
||||
promptRemove(actionData) {
|
||||
// 'promptRemove' is the exact name of an action for AlertmanagerConfig
|
||||
// and this method executes the action.
|
||||
// Get the name of the receiver to delete from the action info.
|
||||
const nameOfReceiverToDelete = actionData.route.query.receiverName;
|
||||
// Remove it from the configuration of the parent AlertmanagerConfig
|
||||
// resource.
|
||||
const existingReceivers = this.alertmanagerConfigResource.spec.receivers || [];
|
||||
const receiversMinusDeletedItem = existingReceivers.filter((receiver) => {
|
||||
return receiver.name !== nameOfReceiverToDelete;
|
||||
});
|
||||
|
||||
this.alertmanagerConfigResource.spec.receivers = receiversMinusDeletedItem;
|
||||
// After saving the AlertmanagerConfig, the resource has been deleted.
|
||||
this.alertmanagerConfigResource.save(...arguments);
|
||||
this.$router.push(this.alertmanagerConfigResource._detailLocation);
|
||||
},
|
||||
redirectToReceiverDetail(receiverName) {
|
||||
return this.alertmanagerConfigResource.getReceiverDetailLink(receiverName);
|
||||
},
|
||||
redirectToAlertmanagerConfigDetail() {
|
||||
const route = this.alertmanagerConfigResource._detailLocation;
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header class="header">
|
||||
<div class="title">
|
||||
<div class="primaryheader">
|
||||
<h1>
|
||||
{{ heading }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<div class="actions">
|
||||
<ButtonGroup
|
||||
v-if="viewOptions && mode === view"
|
||||
:value="currentView"
|
||||
:options="viewOptions"
|
||||
@input="handleButtonGroupClick"
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="mode === view"
|
||||
ref="actions"
|
||||
aria-haspopup="true"
|
||||
type="button"
|
||||
class="btn role-multi-action actions"
|
||||
@click="handleReceiverActionMenuClick"
|
||||
>
|
||||
<i class="icon icon-actions" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ResourceYaml
|
||||
v-if="currentView === yaml && alertmanagerConfigResource"
|
||||
ref="resourceyaml"
|
||||
:value="alertmanagerConfigResource"
|
||||
:mode="mode"
|
||||
:initial-yaml-for-diff="null"
|
||||
:yaml="resourceYaml"
|
||||
:offer-preview="mode === edit"
|
||||
:done-route="JSON.stringify(redirectToReceiverDetail(receiverName))"
|
||||
:done-override="alertmanagerConfigDetailRoute"
|
||||
:apply-hooks="alertmanagerConfigResource.applyHooks"
|
||||
@error="e=>$emit('error', e)"
|
||||
/>
|
||||
<ReceiverConfig
|
||||
v-if="(currentView === config || currentView === detail) && alertmanagerConfigResource"
|
||||
:value="receiverValue"
|
||||
:mode="mode"
|
||||
:alertmanager-config-id="alertmanagerConfigId"
|
||||
:alertmanager-config-resource="alertmanagerConfigResource"
|
||||
:save-override="saveOverride"
|
||||
/>
|
||||
<ActionMenu
|
||||
:custom-actions="receiverActions"
|
||||
:open="receiverActionMenuIsOpen"
|
||||
:use-custom-target-element="true"
|
||||
:custom-target-element="actionMenuTargetElement"
|
||||
:custom-target-event="actionMenuTargetEvent"
|
||||
@close="receiverActionMenuIsOpen = false"
|
||||
@goToEdit="goToEdit"
|
||||
@goToEditYaml="goToEditYaml"
|
||||
@promptRemove="promptRemove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.header{
|
||||
H1{
|
||||
flex: 1;
|
||||
}
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'AlertmanagerConfig',
|
||||
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>AlertmanagerConfig</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.header{
|
||||
display: flex;
|
||||
H1{
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,376 @@
|
||||
<script>
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import ColorInput from '@shell/components/form/ColorInput';
|
||||
import TypeDescription from '@shell/components/TypeDescription';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import FileSelector from '@shell/components/form/FileSelector';
|
||||
import SimpleBox from '@shell/components/SimpleBox';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import AsyncButton from '@shell/components/AsyncButton';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { MANAGEMENT } from '@shell/config/types';
|
||||
import { getVendor, setVendor } from '@shell/config/private-label';
|
||||
import { fetchOrCreateSetting } from '@shell/utils/settings';
|
||||
import { SETTING } from '@shell/config/settings';
|
||||
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
||||
import { setFavIcon } from '@shell/utils/favicon';
|
||||
const Color = require('color');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabeledInput, Checkbox, FileSelector, Loading, SimpleBox, AsyncButton, Banner, ColorInput, TypeDescription
|
||||
},
|
||||
async fetch() {
|
||||
const hash = await allHash({
|
||||
uiPLSetting: this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.PL }),
|
||||
uiLogoDarkSetting: fetchOrCreateSetting(this.$store, SETTING.LOGO_DARK, ''),
|
||||
uiLogoLightSetting: fetchOrCreateSetting(this.$store, SETTING.LOGO_LIGHT, ''),
|
||||
uiColorSetting: fetchOrCreateSetting(this.$store, SETTING.PRIMARY_COLOR, ''),
|
||||
uiLinkColorSetting: fetchOrCreateSetting(this.$store, SETTING.LINK_COLOR, ''),
|
||||
uiFaviconSetting: fetchOrCreateSetting(this.$store, SETTING.FAVICON, ''),
|
||||
});
|
||||
|
||||
Object.assign(this, hash);
|
||||
if (hash.uiLogoDarkSetting.value) {
|
||||
try {
|
||||
this.uiLogoDark = hash.uiLogoDarkSetting.value;
|
||||
this.customizeLogo = true;
|
||||
} catch {}
|
||||
}
|
||||
if (hash.uiLogoLightSetting.value) {
|
||||
try {
|
||||
this.uiLogoLight = hash.uiLogoLightSetting.value;
|
||||
this.customizeLogo = true;
|
||||
} catch {}
|
||||
}
|
||||
if (hash.uiFaviconSetting.value) {
|
||||
try {
|
||||
this.uiFavicon = hash.uiFaviconSetting.value;
|
||||
this.customizeFavicon = true;
|
||||
} catch {}
|
||||
}
|
||||
if (hash.uiColorSetting.value) {
|
||||
this.uiColor = Color(hash.uiColorSetting.value).hex();
|
||||
this.customizeColor = true;
|
||||
}
|
||||
if (hash.uiLinkColorSetting.value) {
|
||||
this.uiLinkColor = Color(hash.uiLinkColorSetting.value).hex();
|
||||
this.customizeLinkColor = true;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
vendor: getVendor(),
|
||||
uiPLSetting: {},
|
||||
uiLogoDarkSetting: {},
|
||||
uiLogoDark: '',
|
||||
uiLogoLightSetting: {},
|
||||
uiLogoLight: '',
|
||||
customizeLogo: false,
|
||||
uiFaviconSetting: {},
|
||||
uiFavicon: '',
|
||||
customizeFavicon: false,
|
||||
uiColorSetting: {},
|
||||
uiColor: null,
|
||||
customizeColor: false,
|
||||
uiLinkColorSetting: {},
|
||||
uiLinkColor: null,
|
||||
customizeLinkColor: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
mode() {
|
||||
const schema = this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
|
||||
|
||||
return schema?.resourceMethods?.includes('PUT') ? _EDIT : _VIEW;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let uiColor = getComputedStyle(document.body).getPropertyValue('--primary');
|
||||
let uiLinkColor = getComputedStyle(document.body).getPropertyValue('--link');
|
||||
const suse = document.querySelector('.suse');
|
||||
|
||||
if (suse) {
|
||||
uiColor = getComputedStyle(suse).getPropertyValue('--primary');
|
||||
uiLinkColor = getComputedStyle(suse).getPropertyValue('--link');
|
||||
}
|
||||
// Only set the color to the default if not already set from the custom color
|
||||
this.uiColor = this.uiColor || uiColor.trim();
|
||||
this.uiLinkColor = this.uiLinkColor || uiLinkColor.trim();
|
||||
},
|
||||
methods: {
|
||||
updateLogo(img, key) {
|
||||
this[key] = img;
|
||||
},
|
||||
setError(e) {
|
||||
this.errors = [];
|
||||
this.errors.push(e);
|
||||
},
|
||||
async save(btnCB) {
|
||||
this.uiPLSetting.value = this.uiPLSetting.value.replaceAll(/[\<>&=#()"]/gm, '');
|
||||
if (this.customizeLogo) {
|
||||
this.uiLogoLightSetting.value = this.uiLogoLight;
|
||||
this.uiLogoDarkSetting.value = this.uiLogoDark;
|
||||
} else {
|
||||
this.uiLogoLightSetting.value = '';
|
||||
this.uiLogoDarkSetting.value = '';
|
||||
}
|
||||
if (this.customizeFavicon) {
|
||||
this.uiFaviconSetting.value = this.uiFavicon;
|
||||
} else {
|
||||
this.uiFaviconSetting.value = '';
|
||||
}
|
||||
if (this.customizeColor) {
|
||||
this.uiColorSetting.value = Color(this.uiColor).rgb().string();
|
||||
} else {
|
||||
this.uiColorSetting.value = null;
|
||||
}
|
||||
if (this.customizeLinkColor) {
|
||||
this.uiLinkColorSetting.value = Color(this.uiLinkColor).rgb().string();
|
||||
} else {
|
||||
this.uiLinkColorSetting.value = null;
|
||||
}
|
||||
this.errors = [];
|
||||
try {
|
||||
await Promise.all([
|
||||
this.uiPLSetting.save(),
|
||||
this.uiLogoDarkSetting.save(),
|
||||
this.uiLogoLightSetting.save(),
|
||||
this.uiColorSetting.save(),
|
||||
this.uiLinkColorSetting.save(),
|
||||
this.uiFaviconSetting.save()
|
||||
]);
|
||||
if (this.uiPLSetting.value !== this.vendor) {
|
||||
setVendor(this.uiPLSetting.value);
|
||||
}
|
||||
setFavIcon(this.$store);
|
||||
btnCB(true);
|
||||
} catch (err) {
|
||||
this.errors.push(err);
|
||||
btnCB(false);
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<div v-else>
|
||||
<h1 class="mb-20">
|
||||
{{ t('branding.label') }}
|
||||
</h1>
|
||||
<TypeDescription resource="branding" />
|
||||
<div>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model="uiPLSetting.value"
|
||||
:label="t('branding.uiPL.label')"
|
||||
:mode="mode"
|
||||
:maxlength="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mt-20 mb-5 pb-5">
|
||||
{{ t('branding.logos.label') }}
|
||||
</h3>
|
||||
<label class="text-label">
|
||||
{{ t('branding.logos.tip', {}, true) }}
|
||||
</label>
|
||||
<div class="row mt-10 mb-20">
|
||||
<Checkbox
|
||||
v-model="customizeLogo"
|
||||
:label="t('branding.logos.useCustom')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="customizeLogo"
|
||||
class="row mb-20"
|
||||
>
|
||||
<div class="col logo-container span-6">
|
||||
<div class="mb-10">
|
||||
<FileSelector
|
||||
:byte-limit="20000"
|
||||
:read-as-data-url="true"
|
||||
class="role-secondary"
|
||||
:label="t('branding.logos.uploadLight')"
|
||||
:mode="mode"
|
||||
@error="setError"
|
||||
@selected="updateLogo($event, 'uiLogoLight')"
|
||||
/>
|
||||
</div>
|
||||
<SimpleBox
|
||||
v-if="uiLogoLight || uiLogoDark"
|
||||
class="theme-light mb-10"
|
||||
>
|
||||
<label class="text-muted">{{ t('branding.logos.lightPreview') }}</label>
|
||||
<img
|
||||
class="logo-preview"
|
||||
:src="uiLogoLight ? uiLogoLight : uiLogoDark"
|
||||
>
|
||||
</SimpleBox>
|
||||
</div>
|
||||
<div class="col logo-container span-6">
|
||||
<div class="mb-10">
|
||||
<FileSelector
|
||||
:byte-limit="20000"
|
||||
:read-as-data-url="true"
|
||||
class="role-secondary"
|
||||
:label="t('branding.logos.uploadDark')"
|
||||
:mode="mode"
|
||||
@error="setError"
|
||||
@selected="updateLogo($event, 'uiLogoDark')"
|
||||
/>
|
||||
</div>
|
||||
<SimpleBox
|
||||
v-if="uiLogoDark || uiLogoLight"
|
||||
class="theme-dark mb-10"
|
||||
>
|
||||
<label class="text-muted">{{ t('branding.logos.darkPreview') }}</label>
|
||||
<img
|
||||
class="logo-preview"
|
||||
:src="uiLogoDark ? uiLogoDark : uiLogoLight"
|
||||
>
|
||||
</SimpleBox>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mt-20 mb-5 pb-5">
|
||||
{{ t('branding.favicon.label') }}
|
||||
</h3>
|
||||
<label class="text-label">
|
||||
{{ t('branding.favicon.tip', {}, true) }}
|
||||
</label>
|
||||
<div class="row mt-10 mb-20">
|
||||
<Checkbox
|
||||
v-model="customizeFavicon"
|
||||
:label="t('branding.favicon.useCustom')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="customizeFavicon"
|
||||
class="row mb-20"
|
||||
>
|
||||
<div class="col logo-container span-12">
|
||||
<div class="mb-10">
|
||||
<FileSelector
|
||||
:byte-limit="20000"
|
||||
:read-as-data-url="true"
|
||||
class="role-secondary"
|
||||
:label="t('branding.favicon.upload')"
|
||||
:mode="mode"
|
||||
@error="setError"
|
||||
@selected="updateLogo($event, 'uiFavicon')"
|
||||
/>
|
||||
</div>
|
||||
<SimpleBox v-if="uiFavicon">
|
||||
<label class="text-muted">{{ t('branding.favicon.preview') }}</label>
|
||||
<img
|
||||
class="logo-preview"
|
||||
:src="uiFavicon"
|
||||
>
|
||||
</SimpleBox>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mt-40 mb-5 pb-0">
|
||||
{{ t('branding.color.label') }}
|
||||
</h3>
|
||||
<label class="text-label">
|
||||
{{ t('branding.color.tip', {}, true) }}
|
||||
</label>
|
||||
<div class="row mt-20">
|
||||
<Checkbox
|
||||
v-model="customizeColor"
|
||||
:label="t('branding.color.useCustom')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="customizeColor"
|
||||
class="row mt-20 mb-20"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="uiColor"
|
||||
component-testid="primary"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="mt-40 mb-5 pb-0">
|
||||
{{ t('branding.linkColor.label') }}
|
||||
</h3>
|
||||
<label class="text-label">
|
||||
{{ t('branding.linkColor.tip', {}, true) }}
|
||||
</label>
|
||||
<div class="row mt-20">
|
||||
<Checkbox
|
||||
v-model="customizeLinkColor"
|
||||
:label="t('branding.linkColor.useCustom')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="customizeLinkColor"
|
||||
class="row mt-20 mb-20"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="uiLinkColor"
|
||||
class="col"
|
||||
component-testid="link"
|
||||
/>
|
||||
<span class="col link-example">
|
||||
<a>
|
||||
{{ t('branding.linkColor.example') }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="err in errors">
|
||||
<Banner
|
||||
:key="err"
|
||||
color="error"
|
||||
:label="err"
|
||||
/>
|
||||
</template>
|
||||
<div v-if="mode === 'edit'">
|
||||
<AsyncButton
|
||||
component-testid="branding-apply"
|
||||
class="pull-right mt-20"
|
||||
mode="apply"
|
||||
@click="save"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang='scss'>
|
||||
.link-example {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
a {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.logo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
::v-deep.simple-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-height: 120px;
|
||||
.content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.logo-preview {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
& LABEL {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { HCI } from '../../../../../types';
|
||||
import SerialConsole from '../../../../../components/SerialConsole';
|
||||
import Loading from '@shell/components/Loading';
|
||||
|
||||
export default {
|
||||
components: { SerialConsole, Loading },
|
||||
|
||||
async fetch() {
|
||||
this.rows = await this.$store.dispatch('harvester/findAll', { type: HCI.VMI });
|
||||
},
|
||||
|
||||
data() {
|
||||
return { uid: this.$route.params.uid };
|
||||
},
|
||||
|
||||
computed: {
|
||||
vmi() {
|
||||
const vmiList = this.$store.getters['harvester/all'](HCI.VMI) || [];
|
||||
const vmi = vmiList.find( (VMI) => {
|
||||
return VMI?.metadata?.ownerReferences?.[0]?.uid === this.uid;
|
||||
});
|
||||
|
||||
return vmi;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.$refs.serialConsole.close();
|
||||
});
|
||||
},
|
||||
|
||||
head() {
|
||||
return { title: this.vmi?.metadata?.name };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<SerialConsole
|
||||
v-else
|
||||
ref="serialConsole"
|
||||
v-model="vmi"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
body, #__nuxt, #__layout, main {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script>
|
||||
import { HCI } from '../../../../../types';
|
||||
import NovncConsoleWrapper from '../../../../../components/novnc/NovncConsoleWrapper.vue';
|
||||
import Loading from '@shell/components/Loading';
|
||||
|
||||
export default {
|
||||
components: { NovncConsoleWrapper, Loading },
|
||||
|
||||
async fetch() {
|
||||
this.rows = await this.$store.dispatch('harvester/findAll', { type: HCI.VMI });
|
||||
},
|
||||
|
||||
data() {
|
||||
return { uid: this.$route.params.uid };
|
||||
},
|
||||
|
||||
computed: {
|
||||
vmi() {
|
||||
const vmiList = this.$store.getters['harvester/all'](HCI.VMI) || [];
|
||||
|
||||
const vmi = vmiList.find( (VMI) => {
|
||||
return VMI?.metadata?.ownerReferences?.[0]?.uid === this.uid;
|
||||
});
|
||||
|
||||
return vmi;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.$refs.console.close();
|
||||
});
|
||||
},
|
||||
|
||||
head() {
|
||||
return { title: this.vmi?.metadata?.name };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<NovncConsoleWrapper
|
||||
v-else
|
||||
ref="console"
|
||||
v-model="vmi"
|
||||
class="novnc-wrapper"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
HTML, BODY, MAIN, #__nuxt, #__layout, #app, .vm-console, .vm-console > DIV, .vm-console > DIV > DIV {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
import { PRODUCT_NAME } from '../../../config/harvester';
|
||||
import { HCI } from '../../../types';
|
||||
|
||||
export default {
|
||||
middleware({ redirect, route }) {
|
||||
return redirect({
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: {
|
||||
...route.params,
|
||||
product: PRODUCT_NAME,
|
||||
resource: HCI.DASHBOARD
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
This is harvester Dashboard.
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import ExplorerMembers from '@shell/components/ExplorerMembers';
|
||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../../config/harvester';
|
||||
import { MANAGEMENT } from '@shell/config/types';
|
||||
export default {
|
||||
components: { ExplorerMembers },
|
||||
computed: {
|
||||
createLocationOverride() {
|
||||
return {
|
||||
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
|
||||
params: {
|
||||
product: HARVESTER_PRODUCT,
|
||||
cluster: this.$store.getters['currentCluster']?.id,
|
||||
resource: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template><ExplorerMembers :create-location-override="createLocationOverride" v-bind="$attrs" /></template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { Location } from 'vue-router';
|
||||
import ExplorerProjectsNamespaces from '@shell/components/ExplorerProjectsNamespaces.vue';
|
||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
||||
import { MANAGEMENT, NAMESPACE } from '@shell/config/types';
|
||||
interface Data {
|
||||
createProjectLocation: Location,
|
||||
createNamespaceLocation: Location
|
||||
}
|
||||
export default Vue.extend<Data, any, any, any>({
|
||||
components: { ExplorerProjectsNamespaces },
|
||||
data() {
|
||||
return {
|
||||
createProjectLocation: {
|
||||
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
|
||||
params: {
|
||||
product: HARVESTER_PRODUCT,
|
||||
cluster: this.$store.getters['currentCluster']?.id,
|
||||
resource: MANAGEMENT.PROJECT
|
||||
},
|
||||
},
|
||||
createNamespaceLocation: {
|
||||
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
|
||||
params: {
|
||||
product: HARVESTER_PRODUCT,
|
||||
resource: NAMESPACE
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template><ExplorerProjectsNamespaces v-bind="$attrs" :create-project-location-override="createProjectLocation" :create-namespace-location-override="createNamespaceLocation" /></template>
|
||||
@@ -0,0 +1,251 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
|
||||
import BannerGraphic from '@shell/components/BannerGraphic';
|
||||
import IndentedPanel from '@shell/components/IndentedPanel';
|
||||
import HarvesterSupportBundle from '../../../../dialog/HarvesterSupportBundle';
|
||||
import CommunityLinks from '@shell/components/CommunityLinks';
|
||||
import { SCHEMA } from '@shell/config/types';
|
||||
import { HCI } from '../../../../types';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BannerGraphic,
|
||||
IndentedPanel,
|
||||
CommunityLinks,
|
||||
HarvesterSupportBundle
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
'footer.docs': 'https://docs.harvesterhci.io',
|
||||
'footer.forums': 'https://forums.rancher.com/c/harvester/',
|
||||
'footer.slack': 'https://slack.rancher.io',
|
||||
'footer.issue': 'https://github.com/harvester/harvester/issues/new/choose'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['currentCluster']),
|
||||
|
||||
dev() {
|
||||
try {
|
||||
return this.$store.getters['prefs/get'](PLUGIN_DEVELOPER);
|
||||
} catch {
|
||||
return this.$store.getters['prefs/get'](DEV);
|
||||
}
|
||||
},
|
||||
|
||||
title() {
|
||||
return 'harvester.support.title';
|
||||
},
|
||||
|
||||
showSupportBundle() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return !!this.$store.getters[`${ inStore }/byId`](
|
||||
SCHEMA,
|
||||
HCI.SUPPORT_BUNDLE
|
||||
);
|
||||
},
|
||||
|
||||
internalPrefix() {
|
||||
const host = window.location.host;
|
||||
const prefix = window.location.pathname.replace(this.$route.path, '');
|
||||
const params = this.$route?.params;
|
||||
|
||||
return {
|
||||
host, prefix, params
|
||||
};
|
||||
},
|
||||
|
||||
rancherLink() {
|
||||
const { host, prefix, params } = this.internalPrefix;
|
||||
|
||||
return `https://${ host }${ prefix }/c/${ params.cluster }/explorer`;
|
||||
},
|
||||
|
||||
longhornLink() {
|
||||
const { host, params } = this.internalPrefix;
|
||||
|
||||
return `https://${ host }/k8s/clusters/${ params.cluster }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/#/dashboard`;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.$store.commit('harvester-common/toggleBundleModal', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<BannerGraphic :title="t(title, {}, true)" />
|
||||
|
||||
<IndentedPanel>
|
||||
<div class="content mt-20">
|
||||
<div class="promo">
|
||||
<div
|
||||
v-if="showSupportBundle"
|
||||
class="box mb-20 box-primary"
|
||||
>
|
||||
<h2>
|
||||
{{ t('harvester.modal.bundle.title') }}
|
||||
</h2>
|
||||
<div>
|
||||
<p class="pb-10">
|
||||
{{ t('harvester.modal.bundle.titleDescription') }}
|
||||
</p>
|
||||
<button
|
||||
class="btn role-secondary btn-sm"
|
||||
type="button"
|
||||
@click="open"
|
||||
>
|
||||
{{ t('harvester.modal.bundle.title') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box box-primary"
|
||||
:class="{'mb-20': dev }"
|
||||
>
|
||||
<h2>
|
||||
{{ t('harvester.support.kubeconfig.title') }}
|
||||
</h2>
|
||||
<div>
|
||||
<p class="pb-10">
|
||||
{{ t('harvester.support.kubeconfig.titleDescription') }}
|
||||
</p>
|
||||
<button
|
||||
class="btn role-secondary btn-sm"
|
||||
type="button"
|
||||
@click="currentCluster.downloadKubeConfig()"
|
||||
>
|
||||
{{ t('harvester.support.kubeconfig.title') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="dev"
|
||||
class="row"
|
||||
>
|
||||
<div class="col span-6 box box-primary">
|
||||
<h2>
|
||||
<a
|
||||
rel="nofollow noopener noreferrer"
|
||||
target="_blank"
|
||||
:href="rancherLink"
|
||||
>{{ t('harvester.support.internal.rancher.title') }} <i class="icon icon-external-link" /></a>
|
||||
</h2>
|
||||
<div>
|
||||
<p class="warning">
|
||||
<t
|
||||
k="harvester.support.internal.rancher.titleDescription"
|
||||
:raw="true"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col span-6 box box-primary">
|
||||
<h2>
|
||||
<a
|
||||
rel="nofollow noopener noreferrer"
|
||||
target="_blank"
|
||||
:href="longhornLink"
|
||||
>{{ t('harvester.support.internal.longhorn.title') }} <i class="icon icon-external-link" /></a>
|
||||
</h2>
|
||||
<div>
|
||||
<p class="warning">
|
||||
<t
|
||||
k="harvester.support.internal.longhorn.titleDescription"
|
||||
:raw="true"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="community">
|
||||
<CommunityLinks :link-options="options" />
|
||||
</div>
|
||||
<div class="external">
|
||||
<a
|
||||
href="https://www.suse.com/suse-harvester/support-matrix/all-supported-versions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>{{ t('harvester.support.community.learnMore') }} <i class="icon icon-external-link" /></a>
|
||||
or
|
||||
<a
|
||||
href="https://www.suse.com/products/harvester/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>{{ t('harvester.support.community.pricing') }} <i class="icon icon-external-link" /></a>
|
||||
</div>
|
||||
</div>
|
||||
</IndentedPanel>
|
||||
<HarvesterSupportBundle v-if="showSupportBundle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
display: grid;
|
||||
grid-column-gap: 20px;
|
||||
grid-row-gap: 20px;
|
||||
grid-template-columns: 70% 30%;
|
||||
}
|
||||
|
||||
.only-community {
|
||||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
}
|
||||
|
||||
.community {
|
||||
border-left: 1px solid var(--border);
|
||||
padding-left: 20px;
|
||||
> h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.support-link {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border);
|
||||
|
||||
&.box-primary {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
> h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
> div {
|
||||
font-weight: 300;
|
||||
line-height: 18px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.role-secondary {
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin: 0 -5px 0 -5px;
|
||||
padding: 5px;
|
||||
background-color: var(--warning-banner-bg);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user