Skip to content
Snippets Groups Projects
Commit 477bbf5d authored by Nikos Filinis's avatar Nikos Filinis
Browse files

Update application graph

parent b90cc15a
No related branches found
No related tags found
No related merge requests found
Pipeline #65368 passed with stage
in 3 minutes and 13 seconds
......@@ -3,7 +3,6 @@
<q-page padding v-if="!$route.meta.isChild">
<h6>Application Graphs</h6>
<q-btn label="Create New HDAG" color="primary" @click="showDialog = true" />
<div v-if="isLoading">Loading...</div>
<div v-if="error">{{ error }}</div>
<div v-else class="q-gutter-md" style="display: flex; flex-wrap: wrap">
......@@ -27,7 +26,6 @@
</q-btn>
</div>
</div>
<div><strong>Type:</strong> {{ artifact.artifactType }}</div>
<div><strong>Name:</strong> {{ artifact.descriptor?.name }}</div>
<div><strong>ID:</strong> {{ artifact.descriptor?.id }}</div>
......@@ -43,7 +41,7 @@
dependency.ref.split('/').length - 1
]
}}
({{ dependency.descriptorType }})
({{ dependency.artifactType }})
</li>
</ul>
</div>
......@@ -54,24 +52,20 @@
</q-card>
</div>
</div>
<q-dialog v-model="showDialog">
<q-card style="width: 800px; max-width: 800px">
<q-card-section>
<div class="text-h6">Create New HDAG</div>
</q-card-section>
<q-card-section>
<textarea style="width: 100%" v-model="hdagJson" class="editor-popup"></textarea>
</q-card-section>
<q-card-section>
<q-btn label="Start" color="primary" @click="saveHdag" />
<q-btn label="Cancel" color="primary" flat @click="showDialog = false" />
</q-card-section>
</q-card>
</q-dialog>
<q-dialog v-model="showDescriptorDialog">
<q-card style="width: 800px; max-width: 800px">
<q-card-section class="q-pa-md">
......@@ -84,27 +78,31 @@
</q-card-actions>
</q-card>
</q-dialog>
<!-- Edit Intent Dialog -->
<q-dialog v-model="showEditIntentDialog">
<q-card style="width: 800px; max-width: 800px">
<q-card-section>
<div class="text-h6">Edit Intent</div>
</q-card-section>
<q-card-section>
<div style="display: flex; gap:20px; align-items: center; padding-bottom: 50px; padding-left: 50px;">
<label> <strong>Artifact Intent: </strong></label>
<q-select style="width:150px;" v-model="editedIntent.hdaGraphIntent.type" :options="artifactIntentOptions"
<div style="
display: flex;
gap: 20px;
align-items: center;
padding-bottom: 50px;
padding-left: 50px;
">
<label><strong>Artifact Intent: </strong></label>
<q-select style="width: 150px" v-model="editedIntent.hdaGraphIntent.type" :options="artifactIntentOptions"
@update:model-value="updateServiceOptions" />
</div>
<strong style="padding-left: 50px; padding-bottom: 10px">Services (Deployment Intent):</strong>
<div v-for="(service, sIndex) in editedIntent.services" :key="sIndex" class="q-mb-md"
style="padding-top: 10px; border-top: 1px dashed #ccc">
<div style="padding: 10px 0 10px 50px">
<strong>{{ service.id }}</strong>
</div>
<div style="display: flex; gap: 20px; padding-left: 50px">
<!-- <q-separator /> -->
<strong style="padding-left: 50px; padding-bottom: 10px;">Services:</strong>
<div v-for="(service, index) in editedIntent.services" :key="index" class="q-mb-md"
style="display: flex; justify-content: space-between; align-items: center; padding-top:10px">
<div style="padding-left: 50px;"><strong> {{ service.id }}</strong></div>
<div style="display: flex; gap:20px; padding-right: 50px;">
<div>
<label>CPU:</label>
<q-select v-model="service.deployment.intent.compute.cpu" :options="cpuOptions" />
......@@ -119,7 +117,29 @@
</div>
<div>
<label>GPU Enabled:</label>
<q-select v-model="service.deployment.intent.compute.gpu.enabled" :options="booleanOptions" />
<q-option-group color="primary" v-model="service.deployment.intent.compute.gpu.enabled"
:options="[{ label: '', value: true }]" type="toggle" inline
:disable="editedIntent.hdaGraphIntent.type !== 'custom'" />
</div>
<div v-if="shouldShowDeviceProximity(sIndex)">
<label>Device Proximity Enabled:</label>
<q-option-group color="primary" v-model="service.deployment.intent.network.deviceProximity.enabled"
:options="[{ label: '', value: true }]" type="toggle" inline
:disable="editedIntent.hdaGraphIntent.type !== 'custom'" />
</div>
</div>
<div v-if="service.deployment.intent.network.latencies.length > 0"
style="padding: 10px 0 0 50px; display: flex; gap: 20px; justify-content: flex-start; align-items: center;">
<label>QoS per Connection Point:</label>
<div v-for="(cp, cpIndex) in service.deployment.intent.network
.latencies" :key="cpIndex" style="display: flex; gap: 20px; margin-top: 20px;">
<div style="width:100px">
<strong>{{ cp.connectionPoint }}</strong>
<q-select v-model="cp.qos" :options="qosOptions" style="min-width: 150px" />
</div>
</div>
</div>
</div>
......@@ -131,8 +151,8 @@
</q-card>
</q-dialog>
</q-page>
<router-view />
</div>
<router-view />
</template>
<script setup>
......@@ -141,6 +161,7 @@ import { useRouter } from 'vue-router';
import { QPage, QCard, QCardSection, QDialog } from 'quasar';
import { useFetchArtifacts } from '../hooks/artifacts';
import { useFetchDescriptors } from '../hooks/descriptors';
import yaml from 'js-yaml';
const router = useRouter();
const { isLoading, error, artifacts, fetchArtifacts } = useFetchArtifacts();
......@@ -150,16 +171,21 @@ const descriptorContent = ref('');
const showDialog = ref(false);
const showEditIntentDialog = ref(false);
const editedIntent = ref(null);
onMounted(() => {
fetchArtifacts('HDAG');
});
const cpuOptions = ref(['light', 'small', 'medium', 'large']);
const ramOptions = ref(['light', 'small', 'medium', 'large']);
const storageOptions = ['small', 'medium', 'large'];
const booleanOptions = [true, false];
const artifactIntentOptions = ['security', 'highAvailability', 'highPerformance', 'energyEfficiency'];
const storageOptions = ref(['small', 'medium', 'large']);
const qosOptions = ref(['best-effort', 'low', 'ultralow']);
const artifactIntentOptions = [
'default',
'highAvailability',
'highPerformance',
'energyEfficiency',
'custom',
];
const { descriptor, fetchDescriptor, publishHdag } = useFetchDescriptors();
const hdagJson = ref(
JSON.stringify(
{
......@@ -183,139 +209,204 @@ const hdagJson = ref(
2,
),
);
onMounted(() => {
fetchArtifacts('HDAG');
});
async function showDescriptor(id) {
console.log('id');
await fetchDescriptor(id);
var fileType = '.yaml';
const selectedDescriptor = descriptor.value.find((desc) =>
desc.fileName.endsWith(fileType),
desc.fileName.endsWith('.yaml'),
);
if (descriptor) {
if (selectedDescriptor) {
descriptorTitle.value = selectedDescriptor.fileName;
descriptorContent.value = selectedDescriptor.content;
showDescriptorDialog.value = true;
}
}
function editIntent(artifact) {
// Create a minimal structure for `editedIntent`
editedIntent.value = {
hdaGraphIntent: {
// read whichever of the possible keys is true
type:
Object.keys(artifact.hdaGraphIntent || {}).find(
key => artifact.hdaGraphIntent[key]?.enabled
) || 'security',
},
services: artifact.dependencies.map(service => ({
id: service.ref.split('/').pop(),
async function editIntent(artifact) {
if (!artifact?.descriptor?.id) return;
await fetchDescriptor(artifact.descriptor.id);
const selectedDescriptor = descriptor.value.find((desc) =>
desc.fileName.endsWith('.yaml'),
);
if (!selectedDescriptor) return;
const descJson = yaml.load(selectedDescriptor.content);
const activeIntentType = getActiveIntentType(
descJson.hdaGraph.hdaGraphIntent,
);
const servicesMapped = descJson.hdaGraph.services?.map((svc) => {
const netIntent = svc.deployment?.intent?.network || {};
const computeIntent = svc.deployment?.intent?.compute || {};
let cpArray = netIntent.latencies || [];
if (netIntent.latencies && netIntent.latencies.length > 0) {
cpArray = netIntent.latencies.map((latency) => ({
connectionPoint: latency.connectionPoint,
qos: latency.qos || 'best-effort'
}));
}
return {
id: svc.id,
deployment: {
intent: {
compute: {
cpu: service.deployment?.intent?.compute?.cpu || 'small',
ram: service.deployment?.intent?.compute?.ram || 'small',
storage: service.deployment?.intent?.compute?.storage || 'small',
gpu: {
enabled:
service.deployment?.intent?.compute?.gpu?.enabled || false,
},
},
network: {
deviceProximity: {
enabled:
service.deployment?.intent?.network?.deviceProximity?.enabled ||
false,
enabled: netIntent.deviceProximity?.enabled ?? false,
},
latencies: cpArray,
},
compute: {
cpu: computeIntent.cpu ?? 'small',
ram: computeIntent.ram ?? 'small',
storage: computeIntent.storage ?? 'small',
gpu: {
enabled: computeIntent.gpu?.enabled ?? false,
},
latencies:
service.deployment?.intent?.network?.latencies || [],
connectionPoints:
service.deployment?.intent?.network?.connectionPoints || [],
},
},
},
})),
};
});
editedIntent.value = {
originalDescriptor: selectedDescriptor,
descriptorObject: descJson,
hdaGraphIntent: {
type: activeIntentType,
},
services: servicesMapped || [],
};
updateServiceOptions(editedIntent.value.hdaGraphIntent.type);
updateServiceOptions(activeIntentType);
showEditIntentDialog.value = true;
}
/**
* Called when user changes the artifact intent (e.g. from "security" to "highPerformance").
* Adjusts `cpuOptions` and `ramOptions` as well as all services' CPU/RAM if needed.
*/
function getActiveIntentType(hdaIntent) {
if (!hdaIntent) return 'default';
const keys = Object.keys(hdaIntent);
const foundKey = keys.find((k) => hdaIntent[k]?.enabled === true);
return foundKey || 'default';
}
function updateServiceOptions(selectedIntent) {
let newCpuOptions = [];
let newRamOptions = [];
switch (selectedIntent) {
case 'security':
newCpuOptions = ['small', 'medium'];
newRamOptions = ['small', 'medium'];
break;
case 'highAvailability':
newCpuOptions = ['medium', 'large'];
newRamOptions = ['medium', 'large'];
break;
case 'highPerformance':
newCpuOptions = ['large'];
newRamOptions = ['large'];
break;
case 'energyEfficiency':
newCpuOptions = ['light', 'small'];
newRamOptions = ['light', 'small'];
break;
default:
// Fallback to all possible values
newCpuOptions = ['light', 'small', 'medium', 'large'];
newRamOptions = ['light', 'small', 'medium', 'large'];
break;
}
const intentRules = {
default: {
cpu: ['medium'],
ram: ['medium'],
storage: ['medium'],
gpu: false,
qos: ['best-effort'],
deviceProximity: true,
},
highAvailability: {
cpu: ['light', 'small', 'medium', 'large'],
ram: ['light', 'small', 'medium', 'large'],
storage: ['small', 'medium', 'large'],
gpu: false,
qos: ['best-effort', 'low', 'ultralow'],
deviceProximity: null,
},
highPerformance: {
cpu: ['medium', 'large'],
ram: ['medium', 'large'],
storage: ['large'],
gpu: true,
qos: ['low', 'ultralow'],
deviceProximity: true,
},
energyEfficiency: {
cpu: ['light', 'small', 'medium'],
ram: ['light', 'small', 'medium'],
storage: ['small', 'medium'],
gpu: false,
qos: ['best-effort', 'low'],
deviceProximity: false,
},
custom: {
cpu: ['light', 'small', 'medium', 'large'],
ram: ['light', 'small', 'medium', 'large'],
storage: ['small', 'medium', 'large'],
gpu: null,
qos: ['best-effort', 'low', 'ultralow'],
deviceProximity: null,
},
};
const rule = intentRules[selectedIntent] || intentRules.default;
// Overwrite the reactive arrays so that the dropdowns update
cpuOptions.value.splice(0, cpuOptions.value.length, ...newCpuOptions);
ramOptions.value.splice(0, ramOptions.value.length, ...newRamOptions);
cpuOptions.value.splice(0, cpuOptions.value.length, ...rule.cpu);
ramOptions.value.splice(0, ramOptions.value.length, ...rule.ram);
storageOptions.value.splice(0, storageOptions.value.length, ...rule.storage);
qosOptions.value.splice(0, qosOptions.value.length, ...rule.qos);
// Now "fix" any services whose CPU/RAM are no longer valid
editedIntent.value?.services?.forEach(service => {
const svcCpu = service.deployment.intent.compute.cpu;
const svcRam = service.deployment.intent.compute.ram;
editedIntent.value?.services?.forEach((service, idx) => {
// For string selects, if the current value is not allowed, default it to the first option.
if (!rule.cpu.includes(service.deployment.intent.compute.cpu)) {
service.deployment.intent.compute.cpu = rule.cpu[0];
}
if (!rule.ram.includes(service.deployment.intent.compute.ram)) {
service.deployment.intent.compute.ram = rule.ram[0];
}
if (!rule.storage.includes(service.deployment.intent.compute.storage)) {
service.deployment.intent.compute.storage = rule.storage[0];
}
if (
service.deployment.intent.network.latencies?.length > 0 &&
!rule.qos.includes(service.deployment.intent.network.latencies[0].qos)
) {
service.deployment.intent.network.latencies[0].qos = rule.qos[0];
}
// If the service's current CPU isn't valid, default it to the first in the new array
if (!cpuOptions.value.includes(svcCpu)) {
service.deployment.intent.compute.cpu = cpuOptions.value[0];
// For booleans, only force update if the rule is defined.
if (rule.gpu !== null) {
service.deployment.intent.compute.gpu.enabled = rule.gpu;
}
// Same logic for RAM
if (!ramOptions.value.includes(svcRam)) {
service.deployment.intent.compute.ram = ramOptions.value[0];
if (rule.deviceProximity !== null) {
// For deviceProximity, if enabled by rule, only the first service is true.
if (rule.deviceProximity === true) {
service.deployment.intent.network.deviceProximity.enabled = (idx === 0);
} else {
service.deployment.intent.network.deviceProximity.enabled = false;
}
}
});
}
function shouldShowDeviceProximity(sIndex) {
const intent = editedIntent.value?.hdaGraphIntent?.type;
if (!intent) return false;
if (intent === 'energyEfficiency') {
return false;
}
return sIndex === 0;
}
async function saveIntent() {
const updatedYaml = editedIntent.value; // Convert back to YAML if necessary
await publishHdag(updatedYaml);
const descJson = editedIntent.value.descriptorObject;
const chosen = editedIntent.value.hdaGraphIntent.type;
Object.keys(descJson.hdaGraph.hdaGraphIntent || {}).forEach((k) => {
descJson.hdaGraph.hdaGraphIntent[k].enabled = k === chosen;
});
editedIntent.value.services.forEach((svc) => {
const target = descJson.hdaGraph.services.find((d) => d.id === svc.id);
if (!target) return;
target.deployment.intent.compute.cpu = svc.deployment.intent.compute.cpu;
target.deployment.intent.compute.ram = svc.deployment.intent.compute.ram;
target.deployment.intent.compute.storage =
svc.deployment.intent.compute.storage;
target.deployment.intent.compute.gpu.enabled =
svc.deployment.intent.compute.gpu.enabled;
target.deployment.intent.network.deviceProximity.enabled =
svc.deployment.intent.network.deviceProximity.enabled;
target.deployment.intent.network.latencies =
svc.deployment.intent.network.latencies.map((cpObj) => {
return cpObj;
});
});
const updatedYaml = yaml.dump(descJson);
descriptorContent.value = updatedYaml;
showEditIntentDialog.value = false;
}
async function publishDescriptor() {
await publishHdag(descriptorContent.value);
showDescriptorDialog.value = false;
}
function viewGraph(artifactName) {
router.push(`application_graph/${artifactName}`);
}
function saveHdag() {
publishHdag(hdagJson.value);
showDialog.value = false;
}
function viewGraph(artifactName) {
router.push(`application_graph/${artifactName}`);
}
</script>
<style scoped>
......@@ -346,3 +437,4 @@ function saveHdag() {
height: 500px;
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment