Add app configure resources view
This commit is contained in:
142
dashboard/src/components/app/Resources.vue
Normal file
142
dashboard/src/components/app/Resources.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, FormGroup, TextInput } from 'pankow';
|
||||
import { prettyBinarySize } from 'pankow/utils';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
import SystemModel from '../../models/SystemModel.js';
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const systemModel = SystemModel.create();
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
|
||||
const memoryLimitBusy = ref(false);
|
||||
const memoryLimit = ref(0);
|
||||
const currentMemoryLimit = ref(0);
|
||||
const memoryTicks = ref([]);
|
||||
const cpuQuotaBusy = ref(false);
|
||||
const cpuQuota = ref(0);
|
||||
const currentCpuQuota = ref(0);
|
||||
const devicesBusy = ref(false);
|
||||
const devicesError = ref('');
|
||||
const devices = ref('');
|
||||
const currentDevices = ref('');
|
||||
|
||||
async function onSubmitMemoryLimit() {
|
||||
memoryLimitBusy.value = true;
|
||||
|
||||
const tmp = parseInt(memoryLimit.value);
|
||||
const limit = tmp === memoryTicks.value[0] ? 0 : tmp; // this will reset to app minimum
|
||||
|
||||
const [error] = await appsModel.configure(props.app.id, 'memory_limit', { memoryLimit: limit });
|
||||
if (error) return console.error(error);
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => memoryLimitBusy.value = false, 2000);
|
||||
}
|
||||
|
||||
async function onSubmitCpuQuota() {
|
||||
cpuQuotaBusy.value = true;
|
||||
|
||||
const [error] = await appsModel.configure(props.app.id, 'cpu_quota', { cpuQuota: parseInt(cpuQuota.value) });
|
||||
if (error) return console.error(error);
|
||||
|
||||
currentCpuQuota.value = parseInt(cpuQuota.value);
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => cpuQuotaBusy.value = false, 2000);
|
||||
}
|
||||
|
||||
async function onSubmitDevices() {
|
||||
devicesBusy.value = true;
|
||||
|
||||
const devs = {};
|
||||
devices.value.split(',').forEach(d => {
|
||||
if (!d.trim()) return;
|
||||
devs[d.trim()] = {};
|
||||
});
|
||||
|
||||
const [error] = await appsModel.configure(props.app.id, 'devices', { devices: devs });
|
||||
if (error && error.status === 400) {
|
||||
devicesError.value = error.body.message;
|
||||
return devicesBusy.value = false;
|
||||
} else if (error) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => devicesBusy.value = false, 2000);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const [error, result] = await systemModel.memory();
|
||||
if (error) return console.error(error);
|
||||
|
||||
cpuQuota.value = props.app.cpuQuota;
|
||||
currentCpuQuota.value = result.cpuQuota;
|
||||
devices.value = Object.keys(props.app.devices).join(', ');
|
||||
currentDevices.value = devices.value;
|
||||
|
||||
memoryLimit.value = props.app.memoryLimit || props.app.manifest.memoryLimit || (256 * 1024 * 1024);
|
||||
currentMemoryLimit.value = memoryLimit.value;
|
||||
|
||||
// create ticks starting from manifest memory limit. the memory limit here is just RAM
|
||||
memoryTicks.value = [];
|
||||
// we max system memory and current app memory for the case where the user configured the app on another server with more resources
|
||||
const nearest256m = Math.ceil(Math.max(result.memory, memoryLimit.value) / (256*1024*1024)) * 256 * 1024 * 1024;
|
||||
const startTick = props.app.manifest.memoryLimit || (256 * 1024 * 1024);
|
||||
|
||||
// code below ensure we atleast have 2 ticks to keep the slider usable
|
||||
memoryTicks.value.push(startTick); // start tick
|
||||
for (var i = startTick * 2; i < nearest256m; i *= 2) memoryTicks.value.push(i);
|
||||
memoryTicks.value.push(nearest256m); // end tick
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<FormGroup>
|
||||
<label for="memoryLimitInput">{{ $t('app.resources.memory.title') }} <sup><a href="https://docs.cloudron.io/apps/#memory-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ prettyBinarySize(memoryLimit, 'Default (256 MiB)') }}</b></label>
|
||||
<p>{{ $t('app.resources.memory.description') }}</p>
|
||||
<input type="range" id="memoryLimitInput" v-model="memoryLimit" step="134217728" :min="memoryTicks[0]" :max="memoryTicks[memoryTicks.length-1]" list="memoryLimitTicks" />
|
||||
<datalist id="memoryLimitTicks">
|
||||
<option v-for="value of memoryTicks" :key="value" :value="value"></option>
|
||||
</datalist>
|
||||
</FormGroup>
|
||||
<br/>
|
||||
<Button @click="onSubmitMemoryLimit()" :loading="memoryLimitBusy" :disabled="memoryLimitBusy || memoryLimit === currentMemoryLimit || app.error || app.taskId" v-tooltip="app.error ? 'App is in error state' : (app.taskId ? 'App is busy' : '')">{{ $t('app.resources.memory.resizeAction') }}</Button>
|
||||
|
||||
<hr/>
|
||||
|
||||
<FormGroup>
|
||||
<label for="cpuQuotaInput">{{ $t('app.resources.cpu.title') }} <sup><a href="https://docs.cloudron.io/apps/#cpu-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ cpuQuota + ' %' }}</b></label>
|
||||
<p>{{ $t('app.resources.cpu.description') }}</p>
|
||||
<input type="range" id="cpuQuotaInput" v-model="cpuQuota" step="1" min="1" max="100" list="cpuQuotaTicks" />
|
||||
<datalist id="cpuQuotaTicks">
|
||||
<option value="25"></option>
|
||||
<option value="50"></option>
|
||||
<option value="75"></option>
|
||||
</datalist>
|
||||
</FormGroup>
|
||||
<br/>
|
||||
<Button @click="onSubmitCpuQuota()" :loading="cpuQuotaBusy" :disabled="cpuQuotaBusy || cpuQuota === currentCpuQuota || app.error || app.taskId" v-tooltip="app.error ? 'App is in error state' : (app.taskId ? 'App is busy' : '')">{{ $t('app.resources.cpu.setAction') }}</Button>
|
||||
|
||||
<hr/>
|
||||
|
||||
<form @submit.prevent="onSubmitDevices()" autocomplete="off">
|
||||
<fieldset :disabled="devicesBusy || app.error || app.taskId">
|
||||
<input style="display: none;" type="submit" :disabled="devices === currentDevices"/>
|
||||
<FormGroup>
|
||||
<label for="devicesInput">Devices <sup><a href="https://docs.cloudron.io/apps/#devices" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p>Comma serparated list of devices mounted into the app</p>
|
||||
<TextInput id="devicesInput" v-model="devices" placeholder="/dev/ttyUSB0, /dev/hidraw0, ..."/>
|
||||
<span class="text-danger" v-if="devicesError">{{ devicesError }}</span>
|
||||
</FormGroup>
|
||||
</fieldset>
|
||||
</form>
|
||||
<br/>
|
||||
<Button @click="onSubmitDevices()" :loading="devicesBusy" :disabled="devices === currentDevices || devicesBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">Set Devices</Button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -11,6 +11,7 @@ import { Button, ButtonGroup } from 'pankow';
|
||||
import Info from '../components/app/Info.vue';
|
||||
import Security from '../components/app/Security.vue';
|
||||
import Cron from '../components/app/Cron.vue';
|
||||
import Resources from '../components/app/Resources.vue';
|
||||
import Repair from '../components/app/Repair.vue';
|
||||
import Eventlog from '../components/app/Eventlog.vue';
|
||||
import Updates from '../components/app/Updates.vue';
|
||||
@@ -21,6 +22,7 @@ import { APP_TYPES, ISTATES, RSTATES, HSTATES } from '../constants.js';
|
||||
const appsModel = AppsModel.create();
|
||||
const installationStateLabel = AppsModel.installationStateLabel;
|
||||
|
||||
const busy = ref(true);
|
||||
const id = ref('');
|
||||
const app = ref({});
|
||||
const view = ref('');
|
||||
@@ -122,6 +124,8 @@ onMounted(async () => {
|
||||
await refresh();
|
||||
|
||||
onSetView(parts[1] || 'info');
|
||||
|
||||
busy.value = false;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -132,7 +136,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<div class="configure-outer">
|
||||
<div class="configure-inner">
|
||||
<div class="configure-inner" v-if="!busy">
|
||||
<div class="titlebar">
|
||||
<div style="display: flex; flex-grow: 1;">
|
||||
<img :src="API_ORIGIN + app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'" style="height: 64px; width: 64px; margin-right: 10px;"/>
|
||||
@@ -188,7 +192,7 @@ onBeforeUnmount(() => {
|
||||
<div v-if="view === 'location'"></div>
|
||||
<div v-if="view === 'proxy'"></div>
|
||||
<div v-if="view === 'access'"></div>
|
||||
<div v-if="view === 'resources'"></div>
|
||||
<Resources :app="app" v-if="view === 'resources'"/>
|
||||
<div v-if="view === 'services'"></div>
|
||||
<div v-if="view === 'storage'"></div>
|
||||
<div v-if="view === 'graphs'"></div>
|
||||
|
||||
@@ -102,7 +102,6 @@ const applinkDialog = useTemplateRef('applinkDialog');
|
||||
|
||||
// hook for applinks otherwise it is a link
|
||||
function openAppEdit(app, event) {
|
||||
console.log('app eidt!')
|
||||
if (app.type === APP_TYPES.LINK) {
|
||||
applinkDialog.value.open(app);
|
||||
event.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user