|  | 
|  | 1 | +// @ts-expect-error FIXME: remove when clever-client exports types | 
|  | 2 | +import { ONE_SECOND } from '@clevercloud/client/esm/with-cache.js'; | 
|  | 3 | +import { getAssetUrl } from '../../lib/assets-url.js'; | 
|  | 4 | +import { fakeString } from '../../lib/fake-strings.js'; | 
|  | 5 | +import { notify, notifyError } from '../../lib/notifications.js'; | 
|  | 6 | +import { sendToApi } from '../../lib/send-to-api.js'; | 
|  | 7 | +import { defineSmartComponent } from '../../lib/smart/define-smart-component.js'; | 
|  | 8 | +import { i18n } from '../../translations/translation.js'; | 
|  | 9 | +import '../cc-smart-container/cc-smart-container.js'; | 
|  | 10 | +import './cc-addon-header.js'; | 
|  | 11 | + | 
|  | 12 | +const PROVIDER_ID = 'kubernetes'; | 
|  | 13 | +const FIFTY_MINUTES = 50 * 60 * 1000; | 
|  | 14 | + | 
|  | 15 | +/** | 
|  | 16 | + * @typedef {import('./cc-addon-header.js').CcAddonHeader} CcAddonHeader | 
|  | 17 | + * @typedef {import('./cc-addon-header.types.js').DeploymentStatus} DeploymentStatus | 
|  | 18 | + * @typedef {import('./cc-addon-header.types.js').CcAddonHeaderStateLoaded} CcAddonHeaderStateLoaded | 
|  | 19 | + * @typedef {import('./cc-addon-header.types.js').CcAddonHeaderStateLoading} CcAddonHeaderStateLoading | 
|  | 20 | + * @typedef {import('./cc-addon-header.types.js').KubeInfo} KubeInfo | 
|  | 21 | + * @typedef {import('../cc-zone/cc-zone.types.js').ZoneStateLoaded} ZoneStateLoaded | 
|  | 22 | + * @typedef {import('../../lib/smart/smart-component.types.js').OnContextUpdateArgs<CcAddonHeader>} OnContextUpdateArgs | 
|  | 23 | + * @typedef {import('../../lib/send-to-api.js').ApiConfig} ApiConfig | 
|  | 24 | + */ | 
|  | 25 | + | 
|  | 26 | +defineSmartComponent({ | 
|  | 27 | +  selector: 'cc-addon-header[smart-mode=kubernetes]', | 
|  | 28 | +  params: { | 
|  | 29 | +    apiConfig: { type: Object }, | 
|  | 30 | +    ownerId: { type: String }, | 
|  | 31 | +    clusterId: { type: String }, | 
|  | 32 | +    productStatus: { type: String, optional: true }, | 
|  | 33 | +  }, | 
|  | 34 | + | 
|  | 35 | +  /** @param {OnContextUpdateArgs} args */ | 
|  | 36 | +  onContextUpdate({ context, updateComponent, signal }) { | 
|  | 37 | +    const { apiConfig, ownerId, clusterId, productStatus } = context; | 
|  | 38 | +    const api = new Api({ apiConfig, ownerId, clusterId, signal }); | 
|  | 39 | + | 
|  | 40 | +    updateComponent('state', { | 
|  | 41 | +      type: 'loading', | 
|  | 42 | +      configLink: { | 
|  | 43 | +        href: fakeString(15), | 
|  | 44 | +        fileName: fakeString(15), | 
|  | 45 | +      }, | 
|  | 46 | +      productStatus: fakeString(4), | 
|  | 47 | +    }); | 
|  | 48 | + | 
|  | 49 | +    // clear when the component handled by the smart is disconnected from the DOM | 
|  | 50 | +    const kubeConfigFetchInterval = setInterval(() => { | 
|  | 51 | +      api | 
|  | 52 | +        .getKubeConfig() | 
|  | 53 | +        .then((kubeConfigUrl) => { | 
|  | 54 | +          updateComponent( | 
|  | 55 | +            'state', | 
|  | 56 | +            /** @param {CcAddonHeaderStateLoaded|CcAddonHeaderStateLoading} state */ | 
|  | 57 | +            (state) => { | 
|  | 58 | +              state.configLink = { | 
|  | 59 | +                fileName: 'kubeconfig.yaml', | 
|  | 60 | +                href: kubeConfigUrl, | 
|  | 61 | +              }; | 
|  | 62 | +            }, | 
|  | 63 | +          ); | 
|  | 64 | +        }) | 
|  | 65 | +        .catch((error) => { | 
|  | 66 | +          console.error(error); | 
|  | 67 | +          notify({ | 
|  | 68 | +            intent: 'danger', | 
|  | 69 | +            message: i18n('cc-addon-header.error.fetch-kubeconfig'), | 
|  | 70 | +            options: { | 
|  | 71 | +              timeout: 0, | 
|  | 72 | +              closeable: true, | 
|  | 73 | +            }, | 
|  | 74 | +          }); | 
|  | 75 | +          updateComponent('state', { | 
|  | 76 | +            type: 'error', | 
|  | 77 | +          }); | 
|  | 78 | +        }); | 
|  | 79 | +    }, FIFTY_MINUTES); | 
|  | 80 | + | 
|  | 81 | +    signal.addEventListener('abort', () => { | 
|  | 82 | +      clearInterval(kubeConfigFetchInterval); | 
|  | 83 | +    }); | 
|  | 84 | + | 
|  | 85 | +    api | 
|  | 86 | +      .getKubeInfoWithKubeConfig() | 
|  | 87 | +      .then(({ kubeInfo, kubeConfigUrl, zone }) => { | 
|  | 88 | +        updateComponent('state', { | 
|  | 89 | +          type: 'loaded', | 
|  | 90 | +          providerId: PROVIDER_ID, | 
|  | 91 | +          providerLogoUrl: getAssetUrl('/logos/kubernetes.svg'), | 
|  | 92 | +          name: kubeInfo.name, | 
|  | 93 | +          id: kubeInfo.id, | 
|  | 94 | +          zone, | 
|  | 95 | +          configLink: { | 
|  | 96 | +            href: kubeConfigUrl, | 
|  | 97 | +            fileName: 'kubeconfig.yaml', | 
|  | 98 | +          }, | 
|  | 99 | +          productStatus, | 
|  | 100 | +          deploymentStatus: /** @type {DeploymentStatus} */ (kubeInfo.status.toLowerCase()), | 
|  | 101 | +        }); | 
|  | 102 | +      }) | 
|  | 103 | +      .catch((error) => { | 
|  | 104 | +        console.error(error); | 
|  | 105 | +        notifyError(i18n('cc-addon-header.error')); | 
|  | 106 | +        updateComponent('state', { | 
|  | 107 | +          type: 'error', | 
|  | 108 | +        }); | 
|  | 109 | +      }); | 
|  | 110 | +  }, | 
|  | 111 | +}); | 
|  | 112 | + | 
|  | 113 | +class Api { | 
|  | 114 | +  /** | 
|  | 115 | +   * @param {object} params | 
|  | 116 | +   * @param {ApiConfig} params.apiConfig - API configuration | 
|  | 117 | +   * @param {string} params.ownerId - Owner identifier | 
|  | 118 | +   * @param {string} params.clusterId - Cluster identifier | 
|  | 119 | +   * @param {AbortSignal} params.signal - Signal to abort calls | 
|  | 120 | +   */ | 
|  | 121 | +  constructor({ apiConfig, ownerId, clusterId, signal }) { | 
|  | 122 | +    this._apiConfig = apiConfig; | 
|  | 123 | +    this._ownerId = ownerId; | 
|  | 124 | +    this._clusterId = clusterId; | 
|  | 125 | +    this._signal = signal; | 
|  | 126 | +  } | 
|  | 127 | + | 
|  | 128 | +  /** @returns {Promise<KubeInfo>} */ | 
|  | 129 | +  _getKubeInfo() { | 
|  | 130 | +    return getKubeInfo({ ownerId: this._ownerId, clusterId: this._clusterId }) | 
|  | 131 | +      .then(sendToApi({ apiConfig: this._apiConfig, signal: this._signal, cacheDelay: ONE_SECOND })) | 
|  | 132 | +      .then((kubeInfo) => { | 
|  | 133 | +        if (kubeInfo.status === 'DELETED' || kubeInfo.status === 'DELETING') { | 
|  | 134 | +          throw new Error('This cluster has been deleted'); | 
|  | 135 | +        } | 
|  | 136 | +        return kubeInfo; | 
|  | 137 | +      }); | 
|  | 138 | +  } | 
|  | 139 | + | 
|  | 140 | +  /** | 
|  | 141 | +   * @return {Promise<string>} | 
|  | 142 | +   */ | 
|  | 143 | +  getKubeConfig() { | 
|  | 144 | +    return getKubeConfig({ ownerId: this._ownerId, clusterId: this._clusterId }) | 
|  | 145 | +      .then(sendToApi({ apiConfig: this._apiConfig, signal: this._signal })) | 
|  | 146 | +      .then(({ url }) => url); | 
|  | 147 | +  } | 
|  | 148 | + | 
|  | 149 | +  async getKubeInfoWithKubeConfig() { | 
|  | 150 | +    const kubeInfo = await this._getKubeInfo(); | 
|  | 151 | +    const kubeConfigUrl = await this.getKubeConfig(); | 
|  | 152 | +    /** @type ZoneStateLoaded */ | 
|  | 153 | +    const zone = { | 
|  | 154 | +      type: 'loaded', | 
|  | 155 | +      name: 'par', | 
|  | 156 | +      country: 'France', | 
|  | 157 | +      countryCode: 'FR', | 
|  | 158 | +      city: 'Paris', | 
|  | 159 | +      displayName: null, | 
|  | 160 | +      lat: 48.8566, | 
|  | 161 | +      lon: 2.3522, | 
|  | 162 | +      tags: ['for:applications', 'for:par-only', 'infra:clever-cloud'], | 
|  | 163 | +    }; | 
|  | 164 | + | 
|  | 165 | +    return { kubeInfo, kubeConfigUrl, zone }; | 
|  | 166 | +  } | 
|  | 167 | +} | 
|  | 168 | + | 
|  | 169 | +// FIXME: remove and use the clever-client call from the new clever-client | 
|  | 170 | +/** @param {{ ownerId: string, clusterId: string }} params */ | 
|  | 171 | +function getKubeInfo(params) { | 
|  | 172 | +  // no multipath for /self or /organisations/{id} | 
|  | 173 | +  return Promise.resolve({ | 
|  | 174 | +    method: 'get', | 
|  | 175 | +    url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`, | 
|  | 176 | +    // no queryParams | 
|  | 177 | +    // no body | 
|  | 178 | +  }); | 
|  | 179 | +} | 
|  | 180 | + | 
|  | 181 | +// FIXME: remove and use the clever-client call from the new clever-client | 
|  | 182 | +/** @param {{ ownerId: string, clusterId: string }} params */ | 
|  | 183 | +function getKubeConfig(params) { | 
|  | 184 | +  // no multipath for /self or /organisations/{id} | 
|  | 185 | +  return Promise.resolve({ | 
|  | 186 | +    method: 'get', | 
|  | 187 | +    url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/kubeconfig/presigned-url`, | 
|  | 188 | +    // no queryParams | 
|  | 189 | +    // no body | 
|  | 190 | +  }); | 
|  | 191 | +} | 
0 commit comments