From 84bf1a30c1c8d629539bdfbfb807b2aa10736103 Mon Sep 17 00:00:00 2001 From: pbochynski Date: Thu, 14 Nov 2024 16:15:12 +0100 Subject: [PATCH] Create instance --- examples/web-component-ext/script.js | 288 ++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 32 deletions(-) diff --git a/examples/web-component-ext/script.js b/examples/web-component-ext/script.js index aede135632..eea2866af7 100644 --- a/examples/web-component-ext/script.js +++ b/examples/web-component-ext/script.js @@ -4,27 +4,66 @@ class MyComponent1 extends HTMLElement { let style = styling(); shadow.appendChild(style); - // create elements programmatically - const button = document.createElement('ui5-button'); - button.textContent = 'Load offerings'; - button.design = 'Emphasized'; - shadow.appendChild(button); const contentDiv = document.createElement('div'); shadow.appendChild(contentDiv); - button.addEventListener('click', async () => { - const fetchFn = window.kymaFetchFn; - if (!fetchFn) { - alert('Kyma fetch function is not available'); - return; - } - let secret = await getSMsecret(); - let token = await getServiceManagerToken(secret); - let offerings = await getServiceOfferings(token, secret.sm_url); - contentDiv.innerHTML = ''; - for (let i = 0; i < offerings.length; i++) { - contentDiv.appendChild(offeringCard(offerings[i], i, offerings.length)); - } - }); + contentDiv.innerHTML = 'loading...'; + loadData(contentDiv); + } +} +function sortByInstanceCountAndOfferingName(a, b) { + if (a.instances.length > b.instances.length) { + return -1; + } + if (a.instances.length < b.instances.length) { + return 1; + } + if (a.metadata.displayName < b.metadata.displayName) { + return -1; + } + if (a.metadata.displayName > b.metadata.displayName) { + return 1; + } + return 0; +} +async function loadData(contentDiv) { + let secret = await getSMsecret(); + let token = await getServiceManagerToken(secret); + const tasks = [ + getServiceOfferings(token, secret.sm_url), + getServicePlans(token, secret.sm_url), + getServiceInstances(), + getServiceBindings(), + getNamespaces(), + ]; + let results = await Promise.allSettled(tasks); + let offerings = results[0].value; + let plans = results[1].value; + let instances = results[2].value; + let bindings = results[3].value; + let namespaces = results[4].value; + console.log(offerings, plans, instances, bindings, namespaces); + for (let offering of offerings) { + offering.namespaces = namespaces; + offering.instances = instances.filter( + instance => instance.spec.serviceOfferingName === offering.name, + ); + offering.plans = plans.filter( + plan => plan.service_offering_id === offering.id, + ); + for (let instance of offering.instances) { + instance.bindings = bindings.filter( + binding => + binding.spec.serviceInstanceName === instance.metadata.name && + binding.metadata.namespace === instance.metadata.namespace, + ); + } + } + offerings = offerings.sort(sortByInstanceCountAndOfferingName); + + contentDiv.innerHTML = ''; + console.log(offerings); + for (let offering of offerings) { + contentDiv.appendChild(offeringCard(offering)); } } @@ -34,6 +73,11 @@ function styling() { .content-group { padding: 1rem; } + .popover-content { + display: flex; + flex-direction: column; + gap: 1rem; + } `; return style; } @@ -58,6 +102,136 @@ async function getSMsecret() { return secret; } +async function createServiceInstance(name, namespace, offering, plan) { + const serviceInstance = { + apiVersion: 'services.cloud.sap.com/v1', + kind: 'ServiceInstance', + metadata: { + name: name, + namespace: namespace, + }, + spec: { + serviceOfferingName: offering, + servicePlanName: plan, + }, + }; + let relativeUrl = `/apis/services.cloud.sap.com/v1/namespaces/${namespace}/serviceinstances`; + try { + let resp = await window.kymaFetchFn({ + relativeUrl, + init: { + method: 'POST', + body: JSON.stringify(serviceInstance), + headers: { + 'Content-Type': 'application/json', + }, + }, + }); + console.log(resp); + return { status: 'ok', message: 'Service instance created' }; + } catch (e) { + return { + status: 'error', + message: `Failed to create service instance: ${e}`, + }; + } +} + +async function getServiceInstances() { + let relativeUrl = '/apis/services.cloud.sap.com/v1/serviceinstances'; + let resp = await window.kymaFetchFn({ relativeUrl }); + let data = await resp.json(); + return data.items; +} +async function getServiceBindings() { + let relativeUrl = '/apis/services.cloud.sap.com/v1/servicebindings'; + let resp = await window.kymaFetchFn({ relativeUrl }); + let data = await resp.json(); + return data.items; +} +async function getNamespaces() { + let relativeUrl = '/api/v1/namespaces'; + let resp = await window.kymaFetchFn({ relativeUrl }); + let data = await resp.json(); + return data.items; +} + +function label(text, forId, required) { + const label = document.createElement('ui5-label'); + label.setAttribute('for', forId); + if (required) { + label.setAttribute('required', ''); + } + label.innerHTML = text; + return label; +} +function dropdown(id, items) { + const dropdown = document.createElement('ui5-select'); + dropdown.id = id; + for (let item of items) { + const option = document.createElement('ui5-option'); + option.value = item; + option.innerHTML = item; + dropdown.appendChild(option); + } + return dropdown; +} + +function createPopover(namespaces, plans, onOk) { + const popover = document.createElement('ui5-popover'); + popover.setAttribute('header-text', 'Create Instance'); + popover.setAttribute('placement', 'Bottom'); + const content = document.createElement('div'); + content.setAttribute('class', 'popover-content'); + content.appendChild(label('Name', 'instanceName', true)); + const instanceName = document.createElement('ui5-input'); + instanceName.id = 'instanceName'; + content.appendChild(instanceName); + + content.appendChild(label('Namespace', 'namespaceDropdown', true)); + const namespaceDropdown = dropdown( + 'namespaceDropdown', + namespaces.map(ns => ns.metadata.name), + ); + content.appendChild(namespaceDropdown); + + content.appendChild(label('Plan', 'planDropdown', true)); + const planDropdown = dropdown( + 'planDropdown', + plans.map(plan => plan.name), + ); + content.appendChild(planDropdown); + + popover.appendChild(content); + const footer = document.createElement('div'); + footer.setAttribute('slot', 'footer'); + footer.setAttribute('class', 'popover-footer'); + const closePopoverButton = document.createElement('ui5-button'); + closePopoverButton.id = 'closePopoverButton'; + closePopoverButton.setAttribute('design', 'Emphasized'); + closePopoverButton.innerHTML = 'Close'; + closePopoverButton.addEventListener('click', () => { + popover.open = false; + }); + footer.appendChild(closePopoverButton); + const createInstanceBtn = document.createElement('ui5-button'); + createInstanceBtn.id = 'createInstanceBtn'; + createInstanceBtn.setAttribute('design', 'Emphasized'); + createInstanceBtn.innerHTML = 'Create Instance'; + createInstanceBtn.addEventListener('click', () => { + const namespace = namespaceDropdown.value; + const plan = planDropdown.value; + + onOk(instanceName.value, namespace, plan); + popover.open = false; + }); + + footer.appendChild(createInstanceBtn); + popover.appendChild(footer); + + return popover; +} + async function getServiceManagerToken(secret) { const client_id = secret.clientid; const client_secret = secret.clientsecret; @@ -86,31 +260,40 @@ async function getServiceOfferings(token, url) { let data = await resp.json(); return data.items; } - -function getProxyAddress() { - // dev busola - if (window.location.hostname.startsWith('localhost')) { - return 'http://localhost:3001/proxy'; - // on cluster - } else { - return '/proxy'; - } +async function getServicePlans(token, url) { + let resp = await proxyFetch(url + `/v1/service_plans`, { + method: 'GET', + headers: { + authorization: `Bearer ${token}`, + }, + }); + let data = await resp.json(); + return data.items; } function proxyFetch(url, options = {}) { - let proxyUrl = getProxyAddress() + `?url=${url}`; + let baseUrl = '/proxy'; + if (window.location.hostname.startsWith('localhost')) { + baseUrl = 'http://localhost:3001/proxy'; + } + let proxyUrl = baseUrl + `?url=${url}`; return fetch(proxyUrl, options); } -function offeringCard(offering, index, total) { +function offeringCard(offering) { const greyImg = 'data:image/svg+xml;base64,PHN2ZyBpZD0icGxhY2Vob2xkZXIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDU2IDU2Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6IzVhN2E5NDt9LmNscy0ye2ZpbGw6IzA0OTFkMDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPnBsYWNlaG9sZGVyPC90aXRsZT48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik00Ni45NTMsMjAuNTg4YTQuMzYzLDQuMzYzLDAsMCwwLTEuODM3LS40NTksMy4yOTEsMy4yOTEsMCwwLDAtMy40LDMuMzc2LDQuMDg0LDQuMDg0LDAsMCwwLC45LDIuNjI1LDMuMDExLDMuMDExLDAsMCwwLDIuNSwxLjEyNiwzLjA4NSwzLjA4NSwwLDAsMCwxLjQ2Mi0uMzc1LDcuNTEyLDcuNTEyLDAsMCwwLDEuMzItLjg5MSwxMC4xMzUsMTAuMTM1LDAsMCwxLDEuMjI2LS44OTEsMi4yNywyLjI3LDAsMCwxLDEuMTc5LS4zNzVBMS41LDEuNSwwLDAsMSw1MiwyNi40MTJWMzkuMDcxYTIuODQzLDIuODQzLDAsMCwxLS41NzYsMiwyLjkyNiwyLjkyNiwwLDAsMS0yLjE1OS42MjZxLTIuOTIzLDAtNC4zODUuMDQ3dC0yLjEyMi4wNDdINDEuOTFhMy4zMjEsMy4zMjEsMCwwLDAsLjYuNjQ0LDUuNzE3LDUuNzE3LDAsMCwxLDIuMDc0LDQuMjIsNS4wNTQsNS4wNTQsMCwwLDEtMS42NSwzLjc1MUE1LjMzMSw1LjMzMSwwLDAsMSwzOS4xMTgsNTJhNS42LDUuNiwwLDAsMS00LjA1NS0xLjU0Nyw1LjA3MSw1LjA3MSwwLDAsMS0xLjYtMy44LDQuODYyLDQuODYyLDAsMCwxLC41MTktMi4zLDExLjQwNywxMS40MDcsMCwwLDEsMS41MTYtMS45NywyLjMzMywyLjMzMywwLDAsMCwuNDc1LS42OUgyOC4zM2ExLjM5NCwxLjM5NCwwLDAsMS0xLjA4NC0uNDY5LDIuMDExLDIuMDExLDAsMCwxLS41MTktMS4wMzJWMTUuOTA5YTEuOCwxLjgsMCwwLDEsLjQyNC0xLjE3MiwxLjQ0NCwxLjQ0NCwwLDAsMSwxLjE3OS0uNTE2aDcuNzMzYTEuOTQ5LDEuOTQ5LDAsMCwwLS4zNzctLjU2MmwtLjgtMS4xNzFhOC43ODgsOC43ODgsMCwwLDEtLjg0Ny0xLjUsNC43ODMsNC43ODMsMCwwLDEtLjQwNi0xLjY3NkE1LjM0OCw1LjM0OCwwLDAsMSwzOS4wODEsNGE1LjU1Miw1LjU1MiwwLDAsMSwzLjc5LDEuNTUzQTQuNjM1LDQuNjM1LDAsMCwxLDQ0LjU1LDkuMzQ1Yy0uMDI4LDEuNjg4LTIuMDIzLDQuMTI1LTIuMjQxLDQuMzc1YTEuNTc2LDEuNTc2LDAsMCwwLS4zLjVoNy4yNjFBMi42NSwyLjY1LDAsMCwxLDUyLDE2Ljg0N3Y0LjEyNnEwLDEuNzgyLTEuNywxLjc4MmExLjc0MywxLjc0MywwLDAsMS0xLjMxOS0uNTQ5QTEzLjE1MiwxMy4xNTIsMCwwLDAsNDYuOTUzLDIwLjU4OFpNMjguMzMsMzkuMDcxYS41ODIuNTgyLDAsMCwwLC42Ni42NTdoNy4xNjdhMS41NzksMS41NzksMCwwLDEsMS43OTIsMS43ODEsMi4yMzgsMi4yMzgsMCwwLDEtLjM4NywxLjI1NGMtLjI4My40MDgtLjU4Mi44MTMtLjksMS4yMTlzLS42MTMuODMtLjksMS4yNjZhMi41NDYsMi41NDYsMCwwLDAtLjQyNCwxLjQwNywzLjExNSwzLjExNSwwLDAsMCwxLjEzMSwyLjUzMiw0LjAyMiw0LjAyMiwwLDAsMCwyLjY0MS45MzgsMy43NzYsMy43NzYsMCwwLDAsMi40NTItLjkzOEEzLjExNSwzLjExNSwwLDAsMCw0Mi43LDQ2LjY1NWEyLjU0NiwyLjU0NiwwLDAsMC0uNDI0LTEuNDA3LDEyLjUxMywxMi41MTMsMCwwLDAtLjk0My0xLjI2NnEtLjUxOS0uNjA5LS45NDMtMS4xNzJhMi4yNjEsMi4yNjEsMCwwLDEtLjQ2Mi0xLjMsMS42MTQsMS42MTQsMCwwLDEsLjU2Ni0xLjMxMywyLjAwNiwyLjAwNiwwLDAsMSwxLjMyLS40NjhoNy40NXEuOTQyLDAsLjk0My0uNjU3VjI2LjUwNmExLjYwOSwxLjYwOSwwLDAsMC0uNzA3LjQyMnEtLjUxOS40MjEtMS4xNzkuODlhMTEuMDY5LDExLjA2OSwwLDAsMS0xLjUwOS44OTEsMy43NywzLjc3LDAsMCwxLTEuNy40MjIsNS40NSw1LjQ1LDAsMCwxLTMuNjc4LTEuNSw0LjI1LDQuMjUsMCwwLDEtMS4yMjYtMS44NzYsNy4wNTMsNy4wNTMsMCwwLDEtLjM3Ny0yLjI1LDUuMTY2LDUuMTY2LDAsMCwxLDEuNi0zLjcsNS4wMDksNS4wMDksMCwwLDEsMy42NzgtMS42NDEsNC44ODQsNC44ODQsMCwwLDEsMi4zNTcuNTE1QTcuNTg3LDcuNTg3LDAsMCwxLDQ5LjUxOCwyMC4yYy41MDYuNTg4Ljc4NS42MjQuNzg1LjYyNFYxNi44NDdhLjU0NC41NDQsMCwwLDAtLjMzMS0uNDY5LDEuNDIyLDEuNDIyLDAsMCwwLS43MDctLjE4N2gtNy40NWEyLjE0NywyLjE0NywwLDAsMS0xLjMyLS40MjIsMS41ODcsMS41ODcsMCwwLDEtLjU2Ni0xLjM2LDIuMDY3LDIuMDY3LDAsMCwxLC40MjUtMS4xNzJxLjQyNS0uNjA5Ljk0My0xLjIxOWExMi4yMjIsMTIuMjIyLDAsMCwwLC45NDMtMS4yNjYsMi41NDEsMi41NDEsMCwwLDAsLjQyNC0xLjQwNywzLjExOCwzLjExOCwwLDAsMC0xLjEzMi0yLjUzMiwzLjc3MSwzLjc3MSwwLDAsMC0yLjQ1MS0uOTM4LDMuODM5LDMuODM5LDAsMCwwLTIuNTk0LjkzOEEzLjE3OCwzLjE3OCwwLDAsMCwzNS40LDkuMzQ1YTIuNzc2LDIuNzc2LDAsMCwwLC40MjQsMS40NTQsMTAuMDM3LDEwLjAzNywwLDAsMCwuOSwxLjI2NWwuODQ5LDEuMjJhMi45MDksMi45MDksMCwwLDEsLjQ3MSwxLjEyNSwxLjYyNSwxLjYyNSwwLDAsMS0uNTE4LDEuMzYsMS45NTYsMS45NTYsMCwwLDEtMS4yNzQuNDIySDI5LjA4NHEtLjc1NSwwLS43NTQuNjU2Wm0yMy42NywwYTIuNywyLjcsMCwwLDEtLjU3NiwyLDIuNjc1LDIuNjc1LDAsMCwxLTIuMTU5LjYyNiIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTM3LjE0NywzMS4wNzRhMy4zMjgsMy4zMjgsMCwwLDAtMi44NzgtMS4zNiw0LjQ0NSw0LjQ0NSwwLDAsMC0yLjEyLjQyMiw2LjE4NSw2LjE4NSwwLDAsMC0xLjE3OC44OTFxLS41NjcuNDcxLTEuMTMyLjg5MWMtLjM3My4yNzgtLjgwOC43NzMtMS4zLjc3NkgyNi43MjdWMTYuNDZhMy4zMzUsMy4zMzUsMCwwLDAtLjM3Ny0xLjUsMS40MzYsMS40MzYsMCwwLDAtMS40MTUtLjc1MUgxOS4yNzdjLS41LDAtLjc1NC4yNTEtLjc1NC44NDRhMS45MDcsMS45MDcsMCwwLDAsLjM3NywxLjEyNiw5LjE0Niw5LjE0NiwwLDAsMCwuOTQzLDEuMTI1LDUuMzQxLDUuMzQxLDAsMCwxLC45NDMsMS4yNjYsMy4yMzYsMy4yMzYsMCwwLDEsLjM3NywxLjU0Nyw0LjQ1NCw0LjQ1NCwwLDAsMS0xLjI3MywzLjE0MSw0LjA0OSw0LjA0OSwwLDAsMS0zLjA2NSwxLjM2LDMuOSwzLjksMCwwLDEtMy4wMTgtMS4zNiw0LjU0Nyw0LjU0NywwLDAsMS0xLjIyNS0zLjE0MSwyLjkzNiwyLjkzNiwwLDAsMSwuNDI0LTEuNTQ3LDEzLjU0OCwxMy41NDgsMCwwLDEsLjktMS4zMTNjLjMxNC0uNDA2LjYyNy0uNzgxLjk0My0xLjEyNWExLjU4OCwxLjU4OCwwLDAsMCwuNDcxLTEuMDc5cTAtLjg0My0xLjAzNy0uODQ0SDUuN2ExLjU4NywxLjU4NywwLDAsMC0xLjIyNi41MTZBMS44MDYsMS44MDYsMCwwLDAsNCwxNS45OTFWMzkuOWExLjgsMS44LDAsMCwwLC40NzEsMS4yNjYsMS41ODMsMS41ODMsMCwwLDAsMS4yMjYuNTE2aDguNDg4Yy42OTEsMCwxLjAzNS4yMzgsMS4wMzcuNzVhMS41NDcsMS41NDcsMCwwLDEtLjQyMi45NDRMMTMuODA3LDQ0LjVhNi41NDksNi41NDksMCwwLDAtLjk5LDEuMjY2LDMuMTE2LDMuMTE2LDAsMCwwLS40MjQsMS42NDEsNC4yMzcsNC4yMzcsMCwwLDAsMS4zNjcsMy40Nyw0Ljc5MSw0Ljc5MSwwLDAsMCw2LjIyNC0uMDQ3LDQuNTE3LDQuNTE3LDAsMCwwLDEuNDQ1LTMuMjgzLDMuNjMxLDMuNjMxLDAsMCwwLS41MTQtMS44ODljLS4yMTUtLjMwNy0uOTc4LTEuMTU4LS45NzgtMS4xNThMMTguOSw0My4zNzNhMS40OTIsMS40OTIsMCwwLDEtLjM3Ny0uOTM4cTAtLjc1Ljg0OC0uNzVoNS42NThxMS4yMjYsMCwxLjctMS41VjM1LjM0MUgyOC4zNWMuNTU3LDAsMS4wNTQuNTE5LDEuNDg5LjhhMTIuMjkxLDEyLjI5MSwwLDAsMSwxLjIyNi44OTFxLjU2NS40NjksMS4xNzkuODlhMy43ODYsMy43ODYsMCwwLDAsMS44MTYuNDIyLDMuMjU2LDMuMjU2LDAsMCwwLDMuMDg3LTEuNDA2LDUuMTE5LDUuMTE5LDAsMCwwLC45OS0zQTQuNzg4LDQuNzg4LDAsMCwwLDM3LjE0NywzMS4wNzRaIi8+PC9zdmc+'; + const msg = toast('ok', 'ok'); let card = document.createElement('ui5-card'); + card.appendChild(msg); let header = document.createElement('ui5-card-header'); // header.slot = 'header'; header.setAttribute('title-text', offering.metadata.displayName); header.setAttribute('subtitle-text', offering.catalog_name); - header.setAttribute('status', `${index + 1} of ${total}`); + const status = `${offering.instances.length} instance${ + offering.instances.length === 1 ? '' : 's' + }`; + header.setAttribute('status', status); const avatar = document.createElement('ui5-avatar'); avatar.slot = 'avatar'; const img = document.createElement('img'); @@ -120,8 +303,49 @@ function offeringCard(offering, index, total) { let cardContent = document.createElement('div'); cardContent.setAttribute('class', 'content-group'); - cardContent.innerHTML = `${offering.description}`; + cardContent.innerHTML = `${offering.description}
`; + + const { btn, popover } = createInstanceButton( + offering, + async (name, namespace, plan) => { + console.log(name, namespace, plan); + const resp = await createServiceInstance( + name, + namespace, + offering.name, + plan, + ); + msg.innerText = `${resp.status}: ${resp.message}`; + msg.open = true; + }, + ); + cardContent.appendChild(btn); + cardContent.appendChild(popover); + card.appendChild(header); card.appendChild(cardContent); return card; } +function toast(status, message) { + const toast = document.createElement('ui5-toast'); + toast.setAttribute('duration', '5000'); + toast.setAttribute('placement', 'TopCenter'); + toast.innerText = `${status}: ${message}`; + return toast; +} + +function createInstanceButton(offering, onOk) { + const btn = document.createElement('ui5-button'); + btn.setAttribute('icon', 'add'); + btn.innerHTML = 'Create Instance'; + btn.setAttribute('design', 'Emphasized'); + //set id to offering name + btn.id = 'btn_' + offering.name; + const popover = createPopover(offering.namespaces, offering.plans, onOk); + + btn.onclick = () => { + popover.setAttribute('opener', btn.id); + popover.open = true; + }; + return { btn, popover }; +}