From e3614a5f54f0cea74fd24e7c3ad7572fc3c49e58 Mon Sep 17 00:00:00 2001 From: Florian Sanders Date: Wed, 29 Oct 2025 10:14:30 +0100 Subject: [PATCH 1/5] feat(cc-addon-credentials-beta): remove add-on specificity from error messages Avoid mentioning the `add-on` term --- src/translations/translations.en.js | 2 +- src/translations/translations.fr.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js index c85f88235..aaaf6176f 100644 --- a/src/translations/translations.en.js +++ b/src/translations/translations.en.js @@ -193,7 +193,7 @@ export const translations = { 'cc-addon-credentials-beta.doc-link.keycloak': `Secured multi-instances - Documentation`, 'cc-addon-credentials-beta.doc-link.otoroshi-api': `Manage Otoroshi from its API - Documentation`, 'cc-addon-credentials-beta.doc-link.otoroshi-ng': `Otoroshi in a Network Group - Documentation`, - 'cc-addon-credentials-beta.error': `Something went wrong while loading add-on information.`, + 'cc-addon-credentials-beta.error': `Something went wrong while loading information`, 'cc-addon-credentials-beta.heading': `Access`, 'cc-addon-credentials-beta.ng-multi-instances.disabling.error': `Something went wrong while trying to disable the secured multi-instances`, 'cc-addon-credentials-beta.ng-multi-instances.disabling.success': `The secured multi-instances have been successfully disabled`, diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js index 8b4afaa39..c3d3e762b 100644 --- a/src/translations/translations.fr.js +++ b/src/translations/translations.fr.js @@ -204,7 +204,7 @@ export const translations = { 'cc-addon-credentials-beta.doc-link.keycloak': `Multi-instances sécurisé - Documentation`, 'cc-addon-credentials-beta.doc-link.otoroshi-api': `Gérer Otoroshi via son API - Documentation`, 'cc-addon-credentials-beta.doc-link.otoroshi-ng': `Otoroshi dans un Network Group - Documentation`, - 'cc-addon-credentials-beta.error': `Une erreur est survenue pendant le chargement des informations de l'add-on.`, + 'cc-addon-credentials-beta.error': `Une erreur est survenue pendant le chargement des informations`, 'cc-addon-credentials-beta.heading': `Accès`, 'cc-addon-credentials-beta.ng-multi-instances.disabling.error': `Une erreur est survenue lors de la désactivation du multi-instances sécurisé`, 'cc-addon-credentials-beta.ng-multi-instances.disabling.success': `Le multi-instances sécurisé a été désactivé avec succès`, From 0f29881ce12cf468ad2d19c1b30e6f259d8d5dde Mon Sep 17 00:00:00 2001 From: Florian Sanders Date: Wed, 29 Oct 2025 10:15:28 +0100 Subject: [PATCH 2/5] feat(cc-addon-header): remove add-on specificity from error messages Avoid mentioning the `add-on` term --- src/translations/translations.en.js | 11 ++++++----- src/translations/translations.fr.js | 15 ++++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js index aaaf6176f..abbbb0b21 100644 --- a/src/translations/translations.en.js +++ b/src/translations/translations.en.js @@ -272,7 +272,7 @@ export const translations = { 'cc-addon-header.action.open-addon': /** @param {{ linkName: string }} _ */ ({ linkName }) => `Open ${linkName}`, 'cc-addon-header.action.restart': `Restart`, 'cc-addon-header.action.restart-rebuild': `Re-build and restart`, - 'cc-addon-header.error': `Something went wrong while loading add-on info.`, + 'cc-addon-header.error': `Something went wrong while loading information`, 'cc-addon-header.logs.link': `View logs`, 'cc-addon-header.rebuild.error': `Something went wrong while rebuilding the add-on.`, 'cc-addon-header.rebuild.success.message': /** @param {{logsUrl: string, docsUrl: string}} _ */ ({ @@ -288,10 +288,11 @@ export const translations = { }) => sanitize`The process of restarting your add-on and its resources is in progress. See the logs or the documentation for more information.`, 'cc-addon-header.restart.success.title': `Restart in progress`, - 'cc-addon-header.state-msg.deployment-failed': `The deployment has failed`, - 'cc-addon-header.state-msg.deployment-is-active': `Your add-on is active!`, - 'cc-addon-header.state-msg.deployment-is-deploying': `Your add-on is deploying…`, - 'cc-addon-header.state-msg.unknown-state': `Unknown state, try to restart the add-on or contact our support if you have additional questions.`, + 'cc-addon-header.state-msg.deployment-failed': `Deployment failed`, + 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ productName: string }} _ */ ({ productName }) => + `${productName} ${productName === 'kubernetes' ? 'cluster' : 'instance'} is active`, + 'cc-addon-header.state-msg.deployment-is-deploying': `Deployment in progress`, + 'cc-addon-header.state-msg.unknown-state': `Unknown state, try to restart or contact our support if you have additional questions.`, //#endregion //#region cc-addon-info 'cc-addon-info.billing.heading': `Billing`, diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js index c3d3e762b..96156bab1 100644 --- a/src/translations/translations.fr.js +++ b/src/translations/translations.fr.js @@ -283,26 +283,27 @@ export const translations = { 'cc-addon-header.action.open-addon': /** @param {{ linkName: string }} _ */ ({ linkName }) => `Ouvrir ${linkName}`, 'cc-addon-header.action.restart': `Redémarrer`, 'cc-addon-header.action.restart-rebuild': `Re-build et redémarrer`, - 'cc-addon-header.error': `Une erreur est survenue pendant le chargement des informations de l'add-on.`, + 'cc-addon-header.error': `Une erreur est survenue pendant le chargement des informations`, 'cc-addon-header.logs.link': `Voir les logs`, - 'cc-addon-header.rebuild.error': `Une erreur est survenue pendant le re-build de l'add-on.`, + 'cc-addon-header.rebuild.error': `Une erreur est survenue pendant le re-build de l'add-on`, 'cc-addon-header.rebuild.success.message': /** @param {{logsUrl: string, docsUrl: string}} _ */ ({ logsUrl, docsUrl, }) => sanitize`Le processus de re-build et de redémarrage de votre add-on et de ses ressources est en cours. Consultez les logs ou la documentation pour plus d'informations.`, 'cc-addon-header.rebuild.success.title': `Re-build et redémarrage en cours`, - 'cc-addon-header.restart.error': `Une erreur est survenue pendant le redémarrage de l'add-on.`, + 'cc-addon-header.restart.error': `Une erreur est survenue pendant le redémarrage de l'add-on`, 'cc-addon-header.restart.success.message': /** @param {{logsUrl: string, docsUrl: string}} _ */ ({ logsUrl, docsUrl, }) => sanitize`Le processus de redémarrage de votre add-on et de ses ressources est en cours. Consultez les logs ou la documentation pour plus d'informations.`, 'cc-addon-header.restart.success.title': `Redémarrage en cours`, - 'cc-addon-header.state-msg.deployment-failed': `Le déploiement de l'add-on a échoué.`, - 'cc-addon-header.state-msg.deployment-is-active': `Votre add-on est disponible !`, - 'cc-addon-header.state-msg.deployment-is-deploying': `L'add-on est en cours de déploiement…`, - 'cc-addon-header.state-msg.unknown-state': `État inconnu, essayez de redémarrer l'add-on ou de contacter notre support si vous avez des questions.`, + 'cc-addon-header.state-msg.deployment-failed': `Le déploiement a échoué`, + 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ productName: string }} _ */ ({ productName }) => + `Votre ${productName === 'kubernetes' ? 'cluster' : 'instance'} ${productName} est disponible !`, + 'cc-addon-header.state-msg.deployment-is-deploying': `En cours de déploiement…`, + 'cc-addon-header.state-msg.unknown-state': `État inconnu, essayez de redémarrer ou de contacter notre support si vous avez des questions`, //#endregion //#region cc-addon-info 'cc-addon-info.billing.heading': `Facturation`, From 69a1127a3b92c2e7872d026ec21c44794361919e Mon Sep 17 00:00:00 2001 From: Florian Sanders Date: Wed, 29 Oct 2025 10:05:25 +0100 Subject: [PATCH 3/5] feat(cc-addon-info): remove add-on specificity from error messages Avoid mentioning the `add-on` term in translations --- src/translations/translations.en.js | 2 +- src/translations/translations.fr.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js index abbbb0b21..ade5e5142 100644 --- a/src/translations/translations.en.js +++ b/src/translations/translations.en.js @@ -303,7 +303,7 @@ export const translations = { 'cc-addon-info.doc-link.matomo': `Matomo - Documentation`, 'cc-addon-info.doc-link.metabase': `Metabase - Documentation`, 'cc-addon-info.doc-link.otoroshi': `Otoroshi - Documentation`, - 'cc-addon-info.error': `Something went wrong while loading add-on information.`, + 'cc-addon-info.error': `Something went wrong while loading information`, 'cc-addon-info.feature.connection-limit': `Connection limit`, 'cc-addon-info.feature.cpu': `vCPUs`, 'cc-addon-info.feature.data-exploration': `Data Exploration`, diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js index 96156bab1..8b9fc106a 100644 --- a/src/translations/translations.fr.js +++ b/src/translations/translations.fr.js @@ -314,7 +314,7 @@ export const translations = { 'cc-addon-info.doc-link.matomo': `Matomo - Documentation`, 'cc-addon-info.doc-link.metabase': `Metabase - Documentation`, 'cc-addon-info.doc-link.otoroshi': `Otoroshi - Documentation`, - 'cc-addon-info.error': `Une erreur est survenue pendant le chargement des informations de l'add-on.`, + 'cc-addon-info.error': `Une erreur est survenue pendant le chargement des informations`, 'cc-addon-info.feature.connection-limit': `Limite de connexions`, 'cc-addon-info.feature.cpu': `vCPUs`, 'cc-addon-info.feature.data-exploration': `Exploration des données`, From 17f14b82335e4e64d2c9c3f7c9848624685b6be3 Mon Sep 17 00:00:00 2001 From: Florian Sanders Date: Thu, 23 Oct 2025 13:19:44 +0200 Subject: [PATCH 4/5] feat(cc-addon-header.smart-kubernetes): init --- demo-smart/index.html | 20 ++ .../cc-addon-header/cc-addon-header.js | 17 +- .../cc-addon-header.smart-kubernetes.js | 191 ++++++++++++++++++ .../cc-addon-header.smart-kubernetes.md | 60 ++++++ .../cc-addon-header.types.d.ts | 16 +- src/translations/translations.en.js | 5 +- src/translations/translations.fr.js | 5 +- 7 files changed, 303 insertions(+), 11 deletions(-) create mode 100644 src/components/cc-addon-header/cc-addon-header.smart-kubernetes.js create mode 100644 src/components/cc-addon-header/cc-addon-header.smart-kubernetes.md diff --git a/demo-smart/index.html b/demo-smart/index.html index a23b959b0..d3d3e0e6a 100644 --- a/demo-smart/index.html +++ b/demo-smart/index.html @@ -208,6 +208,11 @@ >cc-addon-info.smart-metabase +
  • + + cc-addon-header.smart-kubernetes + +
  • @@ -341,6 +346,21 @@ > addon-otoroshi +
    diff --git a/src/components/cc-addon-header/cc-addon-header.js b/src/components/cc-addon-header/cc-addon-header.js index 48a2ee1e1..1a45f6808 100644 --- a/src/components/cc-addon-header/cc-addon-header.js +++ b/src/components/cc-addon-header/cc-addon-header.js @@ -25,6 +25,7 @@ const STATUS_ICON = { deploying: iconDeploying, active: iconActive, failed: iconDeploymentFailed, + deleted: null, }; /** @type {Partial} */ @@ -72,15 +73,16 @@ export class CcAddonHeader extends LitElement { /** * @param {DeploymentStatus} deploymentStatus + * @param {string} providerId * @returns {string} * @private */ - _getStatusMsg(deploymentStatus) { + _getStatusMsg(deploymentStatus, providerId) { if (deploymentStatus === 'deploying') { return i18n('cc-addon-header.state-msg.deployment-is-deploying'); } if (deploymentStatus === 'active') { - return i18n('cc-addon-header.state-msg.deployment-is-active'); + return i18n('cc-addon-header.state-msg.deployment-is-active', { providerId }); } if (deploymentStatus === 'failed') { return i18n('cc-addon-header.state-msg.deployment-failed'); @@ -114,6 +116,7 @@ export class CcAddonHeader extends LitElement { const isRestarting = this.state.type === 'restarting'; const isRebuilding = this.state.type === 'rebuilding'; const deploymentStatus = this.state.deploymentStatus; + const providerId = this.state.type === 'loaded' ? this.state.providerId : ''; return html` @@ -155,9 +158,13 @@ export class CcAddonHeader extends LitElement { `, )} - ${!isStringEmpty(addonInfo.configLink) + ${addonInfo.configLink != null ? html` - ${i18n('cc-addon-header.action.get-config')} ` @@ -200,7 +207,7 @@ export class CcAddonHeader extends LitElement { .icon=${STATUS_ICON[deploymentStatus]} ?skeleton=${skeleton} > - ${this._getStatusMsg(deploymentStatus)} + ${this._getStatusMsg(deploymentStatus, providerId)} ` : ''} ${!isStringEmpty(addonInfo.logsUrl) diff --git a/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.js b/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.js new file mode 100644 index 000000000..218b6467b --- /dev/null +++ b/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.js @@ -0,0 +1,191 @@ +// @ts-expect-error FIXME: remove when clever-client exports types +import { ONE_SECOND } from '@clevercloud/client/esm/with-cache.js'; +import { getAssetUrl } from '../../lib/assets-url.js'; +import { fakeString } from '../../lib/fake-strings.js'; +import { notify, notifyError } from '../../lib/notifications.js'; +import { sendToApi } from '../../lib/send-to-api.js'; +import { defineSmartComponent } from '../../lib/smart/define-smart-component.js'; +import { i18n } from '../../translations/translation.js'; +import '../cc-smart-container/cc-smart-container.js'; +import './cc-addon-header.js'; + +const PROVIDER_ID = 'kubernetes'; +const FIFTY_MINUTES = 50 * 60 * 1000; + +/** + * @typedef {import('./cc-addon-header.js').CcAddonHeader} CcAddonHeader + * @typedef {import('./cc-addon-header.types.js').DeploymentStatus} DeploymentStatus + * @typedef {import('./cc-addon-header.types.js').CcAddonHeaderStateLoaded} CcAddonHeaderStateLoaded + * @typedef {import('./cc-addon-header.types.js').CcAddonHeaderStateLoading} CcAddonHeaderStateLoading + * @typedef {import('./cc-addon-header.types.js').KubeInfo} KubeInfo + * @typedef {import('../cc-zone/cc-zone.types.js').ZoneStateLoaded} ZoneStateLoaded + * @typedef {import('../../lib/smart/smart-component.types.js').OnContextUpdateArgs} OnContextUpdateArgs + * @typedef {import('../../lib/send-to-api.js').ApiConfig} ApiConfig + */ + +defineSmartComponent({ + selector: 'cc-addon-header[smart-mode=kubernetes]', + params: { + apiConfig: { type: Object }, + ownerId: { type: String }, + clusterId: { type: String }, + productStatus: { type: String, optional: true }, + }, + + /** @param {OnContextUpdateArgs} args */ + onContextUpdate({ context, updateComponent, signal }) { + const { apiConfig, ownerId, clusterId, productStatus } = context; + const api = new Api({ apiConfig, ownerId, clusterId, signal }); + + updateComponent('state', { + type: 'loading', + configLink: { + href: fakeString(15), + fileName: fakeString(15), + }, + productStatus: fakeString(4), + }); + + // clear when the component handled by the smart is disconnected from the DOM + const kubeConfigFetchInterval = setInterval(() => { + api + .getKubeConfig() + .then((kubeConfigUrl) => { + updateComponent( + 'state', + /** @param {CcAddonHeaderStateLoaded|CcAddonHeaderStateLoading} state */ + (state) => { + state.configLink = { + fileName: 'kubeconfig.yaml', + href: kubeConfigUrl, + }; + }, + ); + }) + .catch((error) => { + console.error(error); + notify({ + intent: 'danger', + message: i18n('cc-addon-header.error.fetch-kubeconfig'), + options: { + timeout: 0, + closeable: true, + }, + }); + updateComponent('state', { + type: 'error', + }); + }); + }, FIFTY_MINUTES); + + signal.addEventListener('abort', () => { + clearInterval(kubeConfigFetchInterval); + }); + + api + .getKubeInfoWithKubeConfig() + .then(({ kubeInfo, kubeConfigUrl, zone }) => { + updateComponent('state', { + type: 'loaded', + providerId: PROVIDER_ID, + providerLogoUrl: getAssetUrl('/logos/kubernetes.svg'), + name: kubeInfo.name, + id: kubeInfo.id, + zone, + configLink: { + href: kubeConfigUrl, + fileName: 'kubeconfig.yaml', + }, + productStatus, + deploymentStatus: /** @type {DeploymentStatus} */ (kubeInfo.status.toLowerCase()), + }); + }) + .catch((error) => { + console.error(error); + notifyError(i18n('cc-addon-header.error')); + updateComponent('state', { + type: 'error', + }); + }); + }, +}); + +class Api { + /** + * @param {object} params + * @param {ApiConfig} params.apiConfig - API configuration + * @param {string} params.ownerId - Owner identifier + * @param {string} params.clusterId - Cluster identifier + * @param {AbortSignal} params.signal - Signal to abort calls + */ + constructor({ apiConfig, ownerId, clusterId, signal }) { + this._apiConfig = apiConfig; + this._ownerId = ownerId; + this._clusterId = clusterId; + this._signal = signal; + } + + /** @returns {Promise} */ + _getKubeInfo() { + return getKubeInfo({ ownerId: this._ownerId, clusterId: this._clusterId }) + .then(sendToApi({ apiConfig: this._apiConfig, signal: this._signal, cacheDelay: ONE_SECOND })) + .then((kubeInfo) => { + if (kubeInfo.status === 'DELETED' || kubeInfo.status === 'DELETING') { + throw new Error('This cluster has been deleted'); + } + return kubeInfo; + }); + } + + /** + * @return {Promise} + */ + getKubeConfig() { + return getKubeConfig({ ownerId: this._ownerId, clusterId: this._clusterId }) + .then(sendToApi({ apiConfig: this._apiConfig, signal: this._signal })) + .then(({ url }) => url); + } + + async getKubeInfoWithKubeConfig() { + const kubeInfo = await this._getKubeInfo(); + const kubeConfigUrl = await this.getKubeConfig(); + /** @type ZoneStateLoaded */ + const zone = { + type: 'loaded', + name: 'par', + country: 'France', + countryCode: 'FR', + city: 'Paris', + displayName: null, + lat: 48.8566, + lon: 2.3522, + tags: ['for:applications', 'for:par-only', 'infra:clever-cloud'], + }; + + return { kubeInfo, kubeConfigUrl, zone }; + } +} + +// FIXME: remove and use the clever-client call from the new clever-client +/** @param {{ ownerId: string, clusterId: string }} params */ +function getKubeInfo(params) { + // no multipath for /self or /organisations/{id} + return Promise.resolve({ + method: 'get', + url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`, + // no queryParams + // no body + }); +} + +// FIXME: remove and use the clever-client call from the new clever-client +/** @param {{ ownerId: string, clusterId: string }} params */ +function getKubeConfig(params) { + // no multipath for /self or /organisations/{id} + return Promise.resolve({ + method: 'get', + url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/kubeconfig/presigned-url`, + // no queryParams + // no body + }); +} diff --git a/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.md b/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.md new file mode 100644 index 000000000..fb69b306a --- /dev/null +++ b/src/components/cc-addon-header/cc-addon-header.smart-kubernetes.md @@ -0,0 +1,60 @@ +--- +kind: '🛠 Addon/' +title: '💡 Smart (Kubernetes)' +--- +# 💡 Smart `` + +## ℹ️ Details + + +
    Component <cc-addon-header> +
    Selector cc-addon-header[smart-mode="kubernetes"] +
    Requires auth Yes +
    + +## ⚙️ Params + +| Name | Type | Details | Default | +|------------------|-------------|------------------------------------------------------------------------------------------------|----------| +| `apiConfig` | `ApiConfig` | Object with API configuration (target host, tokens...) | | +| `ownerId` | `string` | UUID prefixed with orga_ | | +| `clusterId` | `string` | ID of the Kubernetes cluster prefixed with kubernetes_ | | +| `productStatus` | `string` | Maturity status of the product | Optional | + + + ```ts +interface ApiConfig { + API_HOST: string, + API_OAUTH_TOKEN: string, + API_OAUTH_TOKEN_SECRET: string, + OAUTH_CONSUMER_KEY: string, + OAUTH_CONSUMER_SECRET: string, +} +``` + +## 🌐 API endpoints + +| Method | URL | Cache? | +| ---------- | -------------------------------------------------------------------------------------------- | ------------------------------- | +| `GET` | `/v4/kubernetes/organisations/${ownerId}/clusters/${clusterId}` | Default | +| `GET` | `/v4/kubernetes/organisations/${ownerId}/clusters/${clusterId}/kubeconfig/presigned-url` | Default (fetched every 50 mins) | + + +## ⬇️️ Examples + + ```html + + + +``` diff --git a/src/components/cc-addon-header/cc-addon-header.types.d.ts b/src/components/cc-addon-header/cc-addon-header.types.d.ts index 2b2aaf09e..921bf228f 100644 --- a/src/components/cc-addon-header/cc-addon-header.types.d.ts +++ b/src/components/cc-addon-header/cc-addon-header.types.d.ts @@ -25,7 +25,10 @@ interface OptionalProperties { }; productStatus?: string; deploymentStatus?: DeploymentStatus; - configLink?: string; + configLink?: { + href: string; + fileName: string; + }; } interface OpenLink { @@ -33,7 +36,7 @@ interface OpenLink { name: string; } -export type DeploymentStatus = 'deploying' | 'active' | 'failed'; +export type DeploymentStatus = 'deploying' | 'active' | 'failed' | 'deleted'; export interface CcAddonHeaderStateLoading extends OptionalProperties { type: 'loading'; @@ -68,3 +71,12 @@ export interface RawAddon { } export type Addon = BaseProperties & OptionalProperties; + +export interface KubeInfo { + id: string; + tenantId: string; + name: string; + description?: string | null; + tag?: string | null; + status: string; +} diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js index ade5e5142..a344cbfa5 100644 --- a/src/translations/translations.en.js +++ b/src/translations/translations.en.js @@ -273,6 +273,7 @@ export const translations = { 'cc-addon-header.action.restart': `Restart`, 'cc-addon-header.action.restart-rebuild': `Re-build and restart`, 'cc-addon-header.error': `Something went wrong while loading information`, + 'cc-addon-header.error.fetch-kubeconfig': `An error occurred while refreshing the Kubeconfig link, please refresh the page.`, 'cc-addon-header.logs.link': `View logs`, 'cc-addon-header.rebuild.error': `Something went wrong while rebuilding the add-on.`, 'cc-addon-header.rebuild.success.message': /** @param {{logsUrl: string, docsUrl: string}} _ */ ({ @@ -289,8 +290,8 @@ export const translations = { sanitize`The process of restarting your add-on and its resources is in progress. See the logs or the documentation for more information.`, 'cc-addon-header.restart.success.title': `Restart in progress`, 'cc-addon-header.state-msg.deployment-failed': `Deployment failed`, - 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ productName: string }} _ */ ({ productName }) => - `${productName} ${productName === 'kubernetes' ? 'cluster' : 'instance'} is active`, + 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ providerId: string }} _ */ ({ providerId }) => + `${providerId} ${providerId === 'kubernetes' ? 'cluster' : 'instance'} is active`, 'cc-addon-header.state-msg.deployment-is-deploying': `Deployment in progress`, 'cc-addon-header.state-msg.unknown-state': `Unknown state, try to restart or contact our support if you have additional questions.`, //#endregion diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js index 8b9fc106a..4f1e2812f 100644 --- a/src/translations/translations.fr.js +++ b/src/translations/translations.fr.js @@ -284,6 +284,7 @@ export const translations = { 'cc-addon-header.action.restart': `Redémarrer`, 'cc-addon-header.action.restart-rebuild': `Re-build et redémarrer`, 'cc-addon-header.error': `Une erreur est survenue pendant le chargement des informations`, + 'cc-addon-header.error.fetch-kubeconfig': `Une erreur est survenue lors du rafraîchissement du lien Kubeconfig, veuillez raffraîchir la page`, 'cc-addon-header.logs.link': `Voir les logs`, 'cc-addon-header.rebuild.error': `Une erreur est survenue pendant le re-build de l'add-on`, 'cc-addon-header.rebuild.success.message': /** @param {{logsUrl: string, docsUrl: string}} _ */ ({ @@ -300,8 +301,8 @@ export const translations = { sanitize`Le processus de redémarrage de votre add-on et de ses ressources est en cours. Consultez les logs ou la documentation pour plus d'informations.`, 'cc-addon-header.restart.success.title': `Redémarrage en cours`, 'cc-addon-header.state-msg.deployment-failed': `Le déploiement a échoué`, - 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ productName: string }} _ */ ({ productName }) => - `Votre ${productName === 'kubernetes' ? 'cluster' : 'instance'} ${productName} est disponible !`, + 'cc-addon-header.state-msg.deployment-is-active': /** @param {{ productId: string }} _ */ ({ productId }) => + `Votre ${productId === 'kubernetes' ? 'cluster' : 'instance'} ${productId} est disponible !`, 'cc-addon-header.state-msg.deployment-is-deploying': `En cours de déploiement…`, 'cc-addon-header.state-msg.unknown-state': `État inconnu, essayez de redémarrer ou de contacter notre support si vous avez des questions`, //#endregion From 530a10c6cfe3c1120afe930207d88793a0ca245f Mon Sep 17 00:00:00 2001 From: Florian Sanders Date: Thu, 23 Oct 2025 13:20:24 +0200 Subject: [PATCH 5/5] feat(cc-addon-info.smart-kubernetes): init --- demo-smart/index.html | 5 + .../cc-addon-header.types.d.ts | 2 + .../cc-addon-info.smart-kubernetes.js | 106 ++++++++++++++++++ .../cc-addon-info.smart-kubernetes.md | 56 +++++++++ src/translations/translations.en.js | 1 + src/translations/translations.fr.js | 1 + 6 files changed, 171 insertions(+) create mode 100644 src/components/cc-addon-info/cc-addon-info.smart-kubernetes.js create mode 100644 src/components/cc-addon-info/cc-addon-info.smart-kubernetes.md diff --git a/demo-smart/index.html b/demo-smart/index.html index d3d3e0e6a..931504c48 100644 --- a/demo-smart/index.html +++ b/demo-smart/index.html @@ -213,6 +213,11 @@ cc-addon-header.smart-kubernetes +
  • + + cc-addon-info.smart-kubernetes + +
  • diff --git a/src/components/cc-addon-header/cc-addon-header.types.d.ts b/src/components/cc-addon-header/cc-addon-header.types.d.ts index 921bf228f..d78d6b7c6 100644 --- a/src/components/cc-addon-header/cc-addon-header.types.d.ts +++ b/src/components/cc-addon-header/cc-addon-header.types.d.ts @@ -79,4 +79,6 @@ export interface KubeInfo { description?: string | null; tag?: string | null; status: string; + creationDate: string; + version: string; } diff --git a/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.js b/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.js new file mode 100644 index 000000000..d139bb83f --- /dev/null +++ b/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.js @@ -0,0 +1,106 @@ +// @ts-expect-error FIXME: remove when clever-client exports types +import { ONE_SECOND } from '@clevercloud/client/esm/with-cache.js'; +import { sendToApi } from '../../lib/send-to-api.js'; +import { defineSmartComponent } from '../../lib/smart/define-smart-component.js'; +import { generateDocsHref } from '../../lib/utils.js'; +import { i18n } from '../../translations/translation.js'; +import '../cc-smart-container/cc-smart-container.js'; +import './cc-addon-info.js'; + +/** + * @typedef {import('./cc-addon-info.js').CcAddonInfo} CcAddonInfo + * @typedef {import('./cc-addon-info.types.js').AddonInfoStateLoading} AddonInfoStateLoading + * @typedef {import('../cc-addon-header/cc-addon-header.types.js').KubeInfo} KubeInfo + * @typedef {import('../../lib/smart/smart-component.types.js').OnContextUpdateArgs} OnContextUpdateArgs + * @typedef {import('../../lib/send-to-api.types.js').ApiConfig} ApiConfig + */ + +/** @type {AddonInfoStateLoading} */ +const LOADING_STATE = { + type: 'loading', + version: { + stateType: 'up-to-date', + installed: '0.0.0', + latest: '0.0.0', + }, + creationDate: '2025-08-06 15:03:00', +}; + +defineSmartComponent({ + selector: 'cc-addon-info[smart-mode="kubernetes"]', + params: { + apiConfig: { type: Object }, + ownerId: { type: String }, + clusterId: { type: String }, + }, + /** @param {OnContextUpdateArgs} _ */ + onContextUpdate({ context, updateComponent, signal }) { + const { apiConfig, ownerId, clusterId } = context; + + const api = new Api({ apiConfig, ownerId, clusterId, signal }); + + updateComponent('state', LOADING_STATE); + updateComponent('docLink', { + text: i18n('cc-addon-info.doc-link.kubernetes'), + href: generateDocsHref('/kubernetes'), + }); + + api + .getKubeInfo(ownerId, clusterId) + .then((kubeInfo) => { + updateComponent('state', { + type: 'loaded', + version: { stateType: 'up-to-date', installed: kubeInfo.version, latest: kubeInfo.version }, + creationDate: kubeInfo.creationDate, + }); + }) + .catch((error) => { + console.error(error); + updateComponent('state', { type: 'error' }); + }); + }, +}); + +class Api { + /** + * @param {object} params + * @param {ApiConfig} params.apiConfig - API configuration + * @param {string} params.ownerId - Owner identifier + * @param {string} params.clusterId - Cluster identifier + * @param {AbortSignal} params.signal - Signal to abort calls + */ + constructor({ apiConfig, ownerId, clusterId, signal }) { + this._apiConfig = apiConfig; + this._ownerId = ownerId; + this._clusterId = clusterId; + this._signal = signal; + } + + /** + * @param {string} ownerId + * @param {string} clusterId + * @return {Promise} + */ + getKubeInfo(ownerId, clusterId) { + return getKubeInfo({ ownerId, clusterId }) + .then(sendToApi({ apiConfig: this._apiConfig, signal: this._signal, cacheDelay: ONE_SECOND })) + .then((kubeInfo) => { + if (kubeInfo.status === 'DELETED') { + throw new Error('This cluster has been deleted'); + } + return kubeInfo; + }); + } +} + +// FIXME: remove and use the clever-client call from the new clever-client +/** @param {{ ownerId: string, clusterId: string }} params */ +function getKubeInfo(params) { + // no multipath for /self or /organisations/{id} + return Promise.resolve({ + method: 'get', + url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`, + // no queryParams + // no body + }); +} diff --git a/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.md b/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.md new file mode 100644 index 000000000..908c7997a --- /dev/null +++ b/src/components/cc-addon-info/cc-addon-info.smart-kubernetes.md @@ -0,0 +1,56 @@ +--- +kind: '🛠 Addon/' +title: '💡 Smart (Kubernetes)' +--- +# 💡 Smart `` + +## ℹ️ Details + + +
    Component <cc-addon-info> +
    Selector cc-addon-info[smart-mode="kubernetes"] +
    Requires auth Yes +
    + +## ⚙️ Params + +| Name | Type | Details | Default | +| ------------------- | ----------- |---------------------------------------------------------| ------- | +| `apiConfig` | `ApiConfig` | Object with API configuration (target host, tokens...) | | +| `ownerId` | `string` | UUID prefixed with orga_ | | +| `clusterId` | `string` | ID of the Kubernetes cluster prefixed with kubernetes_ | | + +```ts +interface ApiConfig { + API_HOST: string; + API_OAUTH_TOKEN: string; + API_OAUTH_TOKEN_SECRET: string; + OAUTH_CONSUMER_KEY: string; + OAUTH_CONSUMER_SECRET: string; +} +``` + +## 🌐 API endpoints + +| Method | URL | Cache? | +|----------|------------------------------------------------------------------|---------| +| `GET` | `/v4/kubernetes/organisations/${ownerId}/clusters/${clusterId}` | Default | + + +## ⬇️️ Examples + +```html + + + +``` diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js index a344cbfa5..84074b040 100644 --- a/src/translations/translations.en.js +++ b/src/translations/translations.en.js @@ -301,6 +301,7 @@ export const translations = { 'cc-addon-info.creation-date.human-friendly-date': /** @param {{ date: string | number }} _ */ ({ date }) => formatDatetime(date), 'cc-addon-info.doc-link.keycloak': `Keycloak - Documentation`, + 'cc-addon-info.doc-link.kubernetes': `Kubernetes - Documentation`, 'cc-addon-info.doc-link.matomo': `Matomo - Documentation`, 'cc-addon-info.doc-link.metabase': `Metabase - Documentation`, 'cc-addon-info.doc-link.otoroshi': `Otoroshi - Documentation`, diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js index 4f1e2812f..a61c72d55 100644 --- a/src/translations/translations.fr.js +++ b/src/translations/translations.fr.js @@ -312,6 +312,7 @@ export const translations = { 'cc-addon-info.creation-date.human-friendly-date': /** @param {{ date: string | number }} _ */ ({ date }) => formatDatetime(date), 'cc-addon-info.doc-link.keycloak': `Keycloak - Documentation`, + 'cc-addon-info.doc-link.kubernetes': `Kubernetes - Documentation`, 'cc-addon-info.doc-link.matomo': `Matomo - Documentation`, 'cc-addon-info.doc-link.metabase': `Metabase - Documentation`, 'cc-addon-info.doc-link.otoroshi': `Otoroshi - Documentation`,