├── .nvmrc ├── src ├── components │ ├── cc-button │ │ └── cc-button.types.d.ts │ ├── cc-product-card │ │ └── cc-product-card.types.d.ts │ ├── cc-datetime-relative │ │ └── cc-datetime-relative.types.d.ts │ ├── cc-icon │ │ └── cc-icon.types.d.ts │ ├── cc-overview │ │ └── cc-overview.types.d.ts │ ├── cc-block │ │ └── cc-block.types.d.ts │ ├── cc-env-var-input │ │ ├── cc-env-var-input.types.d.ts │ │ └── cc-env-var-input.events.js │ ├── cc-notice │ │ ├── cc-notice.types.d.ts │ │ └── cc-notice.events.js │ ├── cc-picker-option │ │ └── cc-picker-option.types.d.ts │ ├── cc-select │ │ └── cc-select.types.d.ts │ ├── cc-beta │ │ └── cc-beta.types.d.ts │ ├── cc-map-marker-server │ │ ├── cc-map-marker-server.types.d.ts │ │ └── cc-map-marker-server.stories.js │ ├── cc-popover │ │ └── cc-popover.types.d.ts │ ├── cc-logs │ │ ├── date-display.types.d.ts │ │ ├── cc-logs.events.js │ │ ├── cc-logs.types.d.ts │ │ └── animation-runner.js │ ├── cc-toggle │ │ └── cc-toggle.types.d.ts │ ├── cc-logsmap │ │ ├── cc-logsmap.types.d.ts │ │ └── cc-logsmap.events.js │ ├── cc-picker │ │ └── cc-picker.types.d.ts │ ├── cc-badge │ │ └── cc-badge.types.d.ts │ ├── cc-logs-app-access │ │ └── cc-logs-app-access.types.d.ts │ ├── cc-logs-addon-runtime │ │ └── cc-logs-addon-runtime.types.d.ts │ ├── cc-logs-message-filter │ │ ├── cc-logs-message-filter.types.d.ts │ │ └── cc-logs-message-filter.events.js │ ├── cc-warning-payment │ │ └── cc-warning-payment.types.d.ts │ ├── cc-html-frame │ │ └── cc-html-frame.types.d.ts │ ├── cc-addon-credentials │ │ └── cc-addon-credentials.types.d.ts │ ├── cc-pricing-page │ │ └── cc-pricing-page.types.d.ts │ ├── cc-toast │ │ └── cc-toast.events.js │ ├── cc-zone │ │ └── cc-zone.types.d.ts │ ├── cc-invoice-table │ │ └── cc-invoice-table.types.d.ts │ ├── cc-order-summary │ │ ├── cc-order-summary.events.js │ │ └── cc-order-summary.types.d.ts │ ├── cc-doc-card │ │ └── cc-doc-card.types.d.ts │ ├── cc-input-text │ │ └── cc-input-text.events.js │ ├── cc-doc-list │ │ └── cc-doc-list.types.d.ts │ ├── cc-input-date │ │ └── cc-input-date.types.d.ts │ ├── cc-addon-option │ │ └── cc-addon-option.events.js │ ├── cc-product-list │ │ └── cc-product-list.types.d.ts │ ├── cc-article-card │ │ └── cc-article-card.types.d.ts │ ├── cc-addon-info │ │ └── cc-addon-info.events.js │ ├── cc-invoice-list │ │ ├── cc-invoice-list.types.d.ts │ │ └── cc-invoice-list.smart.js │ ├── cc-matomo-info │ │ └── cc-matomo-info.types.d.ts │ ├── cc-pricing-header │ │ └── cc-pricing-header.types.d.ts │ ├── cc-header-orga │ │ └── cc-header-orga.types.d.ts │ ├── cc-kv-string-editor │ │ ├── cc-kv-string-editor.events.js │ │ └── cc-kv-string-editor.types.d.ts │ ├── cc-env-var-create │ │ ├── cc-env-var-create.events.js │ │ └── cc-env-var-create.stories.js │ ├── cc-article-list │ │ └── cc-article-list.types.d.ts │ ├── cc-tile-scalability │ │ └── cc-tile-scalability.types.d.ts │ ├── cc-logs-instances │ │ ├── cc-logs-instances.events.js │ │ └── cc-logs-instances.types.d.ts │ ├── cc-map │ │ ├── cc-map.types.d.ts │ │ └── cc-map.events.js │ ├── cc-feature-list │ │ ├── cc-feature-list.events.js │ │ └── cc-feature-list.types.d.ts │ ├── cc-pricing-product │ │ └── cc-pricing-product.types.d.ts │ ├── cc-token-api-creation-form │ │ └── cc-token-api-creation-form.events.js │ ├── cc-invoice │ │ └── cc-invoice.types.d.ts │ ├── cc-jenkins-info │ │ └── cc-jenkins-info.types.d.ts │ ├── cc-tile-instances │ │ └── cc-tile-instances.types.d.ts │ ├── cc-addon-option-form │ │ └── cc-addon-option-form.events.js │ ├── cc-plan-picker │ │ └── cc-plan-picker.types.d.ts │ ├── cc-tile-deployments │ │ └── cc-tile-deployments.types.d.ts │ ├── cc-logs-control │ │ ├── cc-logs-control.events.js │ │ └── cc-logs-control.types.d.ts │ ├── cc-clipboard │ │ └── cc-clipboard.stories.js │ ├── cc-zone-picker │ │ └── cc-zone-picker.types.d.ts │ ├── cc-addon-header │ │ └── cc-addon-header.events.js │ ├── cc-header-app │ │ ├── cc-header-app.types.d.ts │ │ └── cc-header-app.events.js │ ├── cc-addon-credentials-content │ │ ├── cc-addon-credentials-content.events.js │ │ └── cc-addon-credentials-content.types.d.ts │ ├── cc-toaster │ │ └── cc-toaster.types.d.ts │ ├── cc-addon-features │ │ └── cc-addon-features.types.d.ts │ ├── cc-addon-linked-apps │ │ └── cc-addon-linked-apps.types.d.ts │ ├── cc-logs-date-range-selector │ │ ├── cc-logs-date-range-selector.events.js │ │ ├── cc-logs-date-range-selector.types.d.ts │ │ └── date-range-selection.js │ ├── cc-heptapod-info │ │ └── cc-heptapod-info.types.d.ts │ ├── cc-tile-requests │ │ └── cc-tile-requests.types.d.ts │ ├── cc-addon-mysql-options │ │ └── cc-addon-mysql-options.stories.js │ ├── cc-addon-redis-options │ │ └── cc-addon-redis-options.stories.js │ ├── cc-visual-tests-report-menu │ │ ├── cc-visual-tests-report-menu.types.d.ts │ │ └── cc-visual-tests-report-menu.stories.js │ ├── cc-env-var-linked-services │ │ └── cc-env-var-linked-services.types.d.ts │ ├── cc-addon-jenkins-options │ │ └── cc-addon-jenkins-options.stories.js │ ├── cc-addon-mongodb-options │ │ └── cc-addon-mongodb-options.stories.js │ ├── cc-elasticsearch-info │ │ └── cc-elasticsearch-info.types.d.ts │ ├── cc-grafana-info │ │ ├── cc-grafana-info.events.js │ │ └── cc-grafana-info.types.d.ts │ ├── cc-addon-postgresql-options │ │ └── cc-addon-postgresql-options.stories.js │ ├── cc-tcp-redirection-form │ │ └── cc-tcp-redirection-form.types.d.ts │ ├── cc-logs-app-runtime │ │ └── cc-logs-app-runtime.types.d.ts │ ├── cc-tcp-redirection │ │ ├── cc-tcp-redirection.types.d.ts │ │ └── cc-tcp-redirection.events.js │ ├── cc-header-addon │ │ └── cc-header-addon.types.d.ts │ ├── cc-token-api-update-form │ │ ├── cc-token-api-update-form.types.d.ts │ │ └── cc-token-api-update-form.events.js │ ├── cc-pricing-product-consumption │ │ └── cc-pricing-product-consumption.types.d.ts │ ├── cc-code │ │ └── cc-code.stories.js │ ├── cc-kv-terminal │ │ ├── cc-kv-terminal.events.js │ │ └── cc-kv-terminal.types.d.ts │ ├── cc-orga-member-list │ │ ├── cc-orga-member-list.types.d.ts │ │ └── cc-orga-member-list.events.js │ ├── cc-pricing-estimation │ │ └── cc-pricing-estimation.types.d.ts │ ├── cc-addon-backups │ │ └── cc-addon-backups.types.d.ts │ ├── cc-env-var-form │ │ ├── cc-env-var-form.types.d.ts │ │ └── cc-env-var-form.events.js │ ├── cc-logs-loading-progress │ │ ├── cc-logs-loading-progress.types.d.ts │ │ └── cc-logs-loading-progress.events.js │ ├── cc-zone-input │ │ └── cc-zone-input.types.d.ts │ ├── cc-addon-admin │ │ └── cc-addon-admin.types.d.ts │ ├── cc-visual-tests-report │ │ └── visual-tests-report.types.d.ts │ ├── cc-token-oauth-list │ │ └── cc-token-oauth-list.types.d.ts │ ├── cc-email-list │ │ └── cc-email-list.types.d.ts │ ├── cc-kv-explorer │ │ └── cc-kv-explorer.smart.md │ ├── cc-ssh-key-list │ │ └── cc-ssh-key-list.events.js │ ├── cc-kv-list-input │ │ └── cc-kv-list-input.stories.js │ ├── cc-token-api-list │ │ └── cc-token-api-list.types.d.ts │ ├── cc-visual-tests-report-entry │ │ └── cc-visual-tests-report-entry.stories.js │ └── cc-kv-set-explorer │ │ └── cc-kv-set-explorer.events.js ├── stories │ ├── assets │ │ ├── console.png │ │ ├── cc-domain-management-empty-mobile-diff.png │ │ ├── cc-article-list-data-loaded-desktop-diff.png │ │ ├── cc-article-list-default-story-mobile-diff.png │ │ ├── cc-domain-management-empty-desktop-actual.png │ │ ├── cc-domain-management-empty-desktop-diff.png │ │ ├── cc-domain-management-empty-mobile-actual.png │ │ ├── cc-addon-backups-default-story-desktop-diff.png │ │ ├── cc-addon-backups-default-story-mobile-diff.png │ │ ├── cc-article-list-data-loaded-desktop-actual.png │ │ ├── cc-article-list-default-story-mobile-actual.png │ │ ├── cc-addon-backups-default-story-desktop-actual.png │ │ ├── cc-addon-backups-default-story-mobile-actual.png │ │ ├── cc-domain-management-default-story-mobile-diff.png │ │ ├── cc-domain-management-empty-desktop-expectation.png │ │ ├── cc-domain-management-empty-mobile-expectation.png │ │ ├── cc-article-list-data-loaded-desktop-expectation.png │ │ ├── cc-article-list-default-story-mobile-expectation.png │ │ ├── cc-domain-management-default-story-desktop-diff.png │ │ ├── cc-domain-management-default-story-mobile-actual.png │ │ ├── cc-addon-backups-default-story-desktop-expectation.png │ │ ├── cc-addon-backups-default-story-mobile-expectation.png │ │ ├── cc-domain-management-default-story-desktop-actual.png │ │ ├── cc-domain-management-default-story-mobile-expectation.png │ │ ├── cc-domain-management-default-story-desktop-expectation.png │ │ ├── oracle.svg │ │ ├── ovh.svg │ │ ├── italic.svg │ │ ├── underline.svg │ │ ├── warning.svg │ │ ├── scaleway.svg │ │ ├── left.svg │ │ ├── right.svg │ │ ├── justify.svg │ │ └── center.svg │ ├── lib │ │ ├── sequence.js │ │ ├── dom.js │ │ ├── timers.js │ │ ├── autodocs-template.jsx │ │ └── i18n-control.js │ └── fixtures │ │ └── fake-map-data.js ├── lib │ ├── leaflet │ │ ├── tsconfig-empty.json │ │ └── leaflet-esm.js │ ├── date │ │ ├── date-range.types.d.ts │ │ └── date.types.d.ts │ ├── color.types.d.ts │ ├── smart │ │ └── smart-symbols.js │ ├── pricing.types.d.ts │ ├── immer.js │ ├── send-to-api.events.js │ ├── tokens.types.d.ts │ ├── assets-url.js │ ├── events.types.d.ts │ ├── css-custom-properties.js │ ├── ansi │ │ ├── ansi-palette-style.js │ │ ├── ansi.types.d.ts │ │ └── palettes │ │ │ ├── hyoob.js │ │ │ ├── default.js │ │ │ ├── everblush.js │ │ │ ├── night-owl.js │ │ │ ├── one-light.js │ │ │ └── tokyo-night-light.js │ ├── form │ │ ├── validation.types.d.ts │ │ ├── form.types.d.ts │ │ ├── form-error-focus-controller.js │ │ └── form.events.js │ ├── notifications.events.js │ ├── remote-assets.js │ ├── fake-strings.js │ ├── focus-helper.js │ ├── animate.js │ ├── send-to-api.types.d.ts │ ├── i18n │ │ ├── i18n.types.d.ts │ │ ├── i18n-display.js │ │ └── i18n-string.js │ ├── regex-parse.js │ ├── notifications.js │ ├── dev-hub-url.js │ ├── dom.js │ └── zone.js ├── assets │ ├── unknown.svg │ ├── mail-line.svg │ ├── start-failed.svg │ ├── running.svg │ ├── mail-star-line.svg │ ├── restarting.svg │ ├── restart-failed.svg │ ├── baseline-widely.svg │ ├── starting.svg │ ├── arrows-left.svg │ ├── arrows-right.svg │ ├── baseline-limited.svg │ ├── git.svg │ ├── baseline-newly.svg │ ├── info.svg │ └── ram.svg ├── styles │ ├── accessibility.js │ ├── waiting.js │ ├── cli-commands.js │ ├── skeleton.js │ └── undefined-components.css ├── translations │ ├── translation.js │ └── translation.types.d.ts └── templates │ └── cc-addon-encryption-at-rest-option │ └── cc-addon-encryption-at-rest-option.js ├── test ├── helpers │ ├── global-mock-random.js │ ├── global-mock-date.js │ ├── mock-now.js │ └── story-testing-utils.types.d.ts ├── dom │ ├── dom.css │ └── dom.test.html ├── smart │ └── smart-manager.test.html └── i18n │ └── i18n-string.test.js ├── .commitlintrc ├── .githooks ├── commit-msg └── pre-push ├── .npmrc ├── custom-elements.json ├── test-mocha └── cem │ └── fixtures │ ├── common.types.d.ts │ ├── cem-test-import │ └── cc-test-imports-sub.types.d.ts │ ├── test-imports.types.d.ts │ └── cc-test-component-with-import.js ├── .gitignore ├── .prettierignore ├── cdn-ui └── cors.xml ├── sandbox ├── forms │ ├── form-demo-with-smart-component.types.d.ts │ └── form-demo-with-array-type.js └── sandbox-styles.js ├── .git-blame-ignore-revs ├── docs ├── copywriting │ └── coming-soon.md ├── guidelines │ └── coming-soon.md ├── contributing │ └── tools.md ├── getting-started │ └── manual-installation.md └── adr │ ├── adr-0004-why-wrap-input-textarea.md │ └── adr-0001-why-do-we-wrap-button-clicks.md ├── eslint ├── lit-a11y │ └── eslint-config-lit-a11y-clever-cloud.js ├── lit │ └── eslint-config-lit-clever-cloud.js ├── wc │ └── eslint-config-wc-clever-cloud.js └── i18n │ └── custom-rules │ └── i18n-valid-key.js ├── .github ├── scripts │ ├── get-stories-groups.js │ └── parse-version-from-tag.js ├── ISSUE_TEMPLATE │ └── accessibility-issue.md └── workflows │ └── release.yml ├── rollup ├── rollup-cdn.config.js ├── rollup-plugin-styles-assets.js ├── rollup-cdn-preview.config.js └── rollup-plugin-index-generator.js ├── .storybook ├── .htaccess └── manager.js ├── public └── favicon.svg ├── web-test-runner ├── cem-analyzer-plugin.js ├── wds-common.js ├── esbuild-bundle-plugin.js ├── story-file-to-a11y-tests-file-plugin.js └── visual-tests │ └── story-file-to-visual-tests-file-plugin.js ├── tasks └── cdn-entry-name.js ├── tsconfig.ci.json ├── cem ├── sort-items.js ├── remove-private-members.js ├── add-github-source-in-description.js └── add-dependencies-in-description.js ├── prettier.config.js ├── custom-elements-manifest.config.mjs └── demo-smart └── index.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /src/components/cc-button/cc-button.types.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-product-card/cc-product-card.types.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-datetime-relative/cc-datetime-relative.types.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/global-mock-random.js: -------------------------------------------------------------------------------- 1 | Math.random = (_) => 0.5; 2 | -------------------------------------------------------------------------------- /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.githooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npx --no -- commitlint --edit 4 | -------------------------------------------------------------------------------- /src/components/cc-icon/cc-icon.types.d.ts: -------------------------------------------------------------------------------- 1 | export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; 2 | -------------------------------------------------------------------------------- /src/components/cc-overview/cc-overview.types.d.ts: -------------------------------------------------------------------------------- 1 | export type OverviewModeType = 'app' | 'orga'; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix = "" 2 | message = "Upgrade to %s" 3 | preid = "beta" 4 | loglevel = "error" 5 | -------------------------------------------------------------------------------- /custom-elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "readme": "", 4 | "modules": [] 5 | } 6 | -------------------------------------------------------------------------------- /src/components/cc-block/cc-block.types.d.ts: -------------------------------------------------------------------------------- 1 | export type BlockToggleState = 'off' | 'open' | 'close'; 2 | -------------------------------------------------------------------------------- /src/components/cc-env-var-input/cc-env-var-input.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface EnvVarName { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/cc-notice/cc-notice.types.d.ts: -------------------------------------------------------------------------------- 1 | export type NoticeIntent = 'info' | 'danger' | 'success' | 'warning'; 2 | -------------------------------------------------------------------------------- /src/components/cc-picker-option/cc-picker-option.types.d.ts: -------------------------------------------------------------------------------- 1 | export type PickerOptionSelectionStyle = 'check' | 'radio'; 2 | -------------------------------------------------------------------------------- /src/components/cc-select/cc-select.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Option { 2 | label: string; 3 | value: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/stories/assets/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/console.png -------------------------------------------------------------------------------- /src/components/cc-beta/cc-beta.types.d.ts: -------------------------------------------------------------------------------- 1 | export type PositionType = 'top-left' | 'bottom-left' | 'top-right' | 'bottom-right'; 2 | -------------------------------------------------------------------------------- /src/components/cc-map-marker-server/cc-map-marker-server.types.d.ts: -------------------------------------------------------------------------------- 1 | export type MarkerStateType = 'default' | 'hovered' | 'selected'; 2 | -------------------------------------------------------------------------------- /test-mocha/cem/fixtures/common.types.d.ts: -------------------------------------------------------------------------------- 1 | interface Common { 2 | sub: SubCommon; 3 | } 4 | 5 | interface SubCommon { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/components/cc-popover/cc-popover.types.d.ts: -------------------------------------------------------------------------------- 1 | export type PopoverPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; 2 | -------------------------------------------------------------------------------- /src/components/cc-logs/date-display.types.d.ts: -------------------------------------------------------------------------------- 1 | export type DateDisplay = 'none' | 'datetime-iso' | 'time-iso' | 'datetime-short' | 'time-short'; 2 | -------------------------------------------------------------------------------- /src/components/cc-toggle/cc-toggle.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Choice { 2 | label: string; 3 | image?: string; // Optional URL of an image 4 | value: string; 5 | } 6 | -------------------------------------------------------------------------------- /test/dom/dom.css: -------------------------------------------------------------------------------- 1 | .big { 2 | height: 5000px; 3 | } 4 | 5 | .container { 6 | height: 250px; 7 | overflow: auto; 8 | } 9 | 10 | .item { 11 | height: 100px; 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.s3cfg 3 | .env* 4 | .preview-index.html 5 | .preview-manifest.json 6 | dist 7 | dist-cdn 8 | node_modules 9 | storybook-static 10 | visual-test-reports 11 | -------------------------------------------------------------------------------- /src/components/cc-logsmap/cc-logsmap.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface PointsOptions { 2 | spreadDuration?: boolean | number; // Spread points appearance over a time window (in ms) 3 | } 4 | -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-mobile-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-mobile-diff.png -------------------------------------------------------------------------------- /src/components/cc-picker/cc-picker.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface PickerOption { 2 | body: string | Node; 3 | disabled?: boolean; 4 | footer?: string | Node; 5 | value?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/leaflet/tsconfig-empty.json: -------------------------------------------------------------------------------- 1 | // prevents esbuild from bundling types instead of the actual package 2 | // because of the `paths` option within the regular `tsconfig.json` file 3 | {} 4 | -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-data-loaded-desktop-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-data-loaded-desktop-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-default-story-mobile-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-default-story-mobile-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-desktop-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-desktop-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-desktop-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-desktop-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-mobile-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-mobile-actual.png -------------------------------------------------------------------------------- /src/assets/unknown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-badge/cc-badge.types.d.ts: -------------------------------------------------------------------------------- 1 | export type BadgeIntent = 'neutral' | 'info' | 'success' | 'warning' | 'danger'; 2 | 3 | export type BadgeWeight = 'strong' | 'dimmed' | 'outlined'; 4 | -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-desktop-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-desktop-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-mobile-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-mobile-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-data-loaded-desktop-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-data-loaded-desktop-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-default-story-mobile-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-default-story-mobile-actual.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore assets 2 | src/assets 3 | src/stories/assets 4 | 5 | # Ignore fixtures 6 | src/stories/fixtures 7 | test-mocha/cem/fixtures 8 | 9 | # Ignore MD files 10 | **/*.md 11 | -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-desktop-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-desktop-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-mobile-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-mobile-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-mobile-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-mobile-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-desktop-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-desktop-expectation.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-empty-mobile-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-empty-mobile-expectation.png -------------------------------------------------------------------------------- /src/lib/date/date-range.types.d.ts: -------------------------------------------------------------------------------- 1 | export type DateRange = { 2 | since: string; 3 | until?: string; 4 | }; 5 | 6 | export type RawDateRange = { 7 | since: Date; 8 | until?: Date; 9 | }; 10 | -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-data-loaded-desktop-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-data-loaded-desktop-expectation.png -------------------------------------------------------------------------------- /src/stories/assets/cc-article-list-default-story-mobile-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-article-list-default-story-mobile-expectation.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-desktop-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-desktop-diff.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-mobile-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-mobile-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-desktop-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-desktop-expectation.png -------------------------------------------------------------------------------- /src/stories/assets/cc-addon-backups-default-story-mobile-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-addon-backups-default-story-mobile-expectation.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-desktop-actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-desktop-actual.png -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-mobile-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-mobile-expectation.png -------------------------------------------------------------------------------- /src/lib/color.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface RgbColor { 2 | r: number; 3 | g: number; 4 | b: number; 5 | } 6 | 7 | export interface HslColor { 8 | h: number; 9 | s: number; 10 | l: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/stories/assets/cc-domain-management-default-story-desktop-expectation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverCloud/clever-components/HEAD/src/stories/assets/cc-domain-management-default-story-desktop-expectation.png -------------------------------------------------------------------------------- /src/assets/mail-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cdn-ui/cors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | GET 4 | * 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/lib/smart/smart-symbols.js: -------------------------------------------------------------------------------- 1 | export const COMPONENTS = Symbol('COMPONENTS'); 2 | export const CURRENT_CONTEXT = Symbol('CURRENT_CONTEXT'); 3 | export const LAST_CONTEXT = Symbol('LAST_CONTEXT'); 4 | export const META = Symbol('META'); 5 | -------------------------------------------------------------------------------- /test-mocha/cem/fixtures/cem-test-import/cc-test-imports-sub.types.d.ts: -------------------------------------------------------------------------------- 1 | import { ToImport } from "../test-imports.types.js"; 2 | 3 | export interface ToSubImport { 4 | 5 | } 6 | 7 | export interface ToSubImportBar { 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/components/cc-logs-app-access/cc-logs-app-access.types.d.ts: -------------------------------------------------------------------------------- 1 | import { LogsStreamState } from '../../lib/logs/logs-stream.types.js'; 2 | 3 | export interface LogsAppAccessState { 4 | type: 'loaded'; 5 | streamState: LogsStreamState; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/pricing.types.d.ts: -------------------------------------------------------------------------------- 1 | import { PricingSection } from 'src/components/common.types.js'; 2 | 3 | export type PricingSimulatorState = Partial<{ 4 | [key in PricingSection['type']]: Omit & { quantity: number }; 5 | }>; 6 | -------------------------------------------------------------------------------- /src/stories/lib/sequence.js: -------------------------------------------------------------------------------- 1 | function wait(delay) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, delay); 4 | }); 5 | } 6 | 7 | export function sequence(callback) { 8 | setTimeout(() => callback(wait), 50); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/cc-logs-addon-runtime/cc-logs-addon-runtime.types.d.ts: -------------------------------------------------------------------------------- 1 | import { LogsStreamState } from '../../lib/logs/logs-stream.types.js'; 2 | 3 | export interface LogsAddonRuntimeState { 4 | type: 'loaded'; 5 | streamState: LogsStreamState; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/cc-logs-message-filter/cc-logs-message-filter.types.d.ts: -------------------------------------------------------------------------------- 1 | import { LogMessageFilterMode } from '../cc-logs/cc-logs.types.js'; 2 | 3 | export interface LogsMessageFilterValue { 4 | value: string; 5 | mode: LogMessageFilterMode; 6 | } 7 | -------------------------------------------------------------------------------- /test-mocha/cem/fixtures/test-imports.types.d.ts: -------------------------------------------------------------------------------- 1 | import { ToSubImport, ToSubImportBar } from "./cem-test-import/cc-test-imports-sub.types.js"; 2 | 3 | export interface ToImport { 4 | 5 | } 6 | 7 | export interface ImpExtendInterface { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/components/cc-warning-payment/cc-warning-payment.types.d.ts: -------------------------------------------------------------------------------- 1 | export type PaymentWarningModeType = 'home' | 'overview' | 'billing'; 2 | 3 | export interface PaymentMethodError { 4 | type: number; 5 | orgaName?: string; 6 | orgaBillingLink?: string; 7 | } 8 | -------------------------------------------------------------------------------- /sandbox/forms/form-demo-with-smart-component.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface FormDemoWithSmartComponentState { 2 | type: 'idle' | 'submitting'; 3 | values?: { 4 | name: string; 5 | email: string; 6 | }; 7 | errors?: { 8 | email: 'email-used'; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/immer.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | // immer uses a non-standard global property from Node.js "process" 4 | window.process = { 5 | env: { 6 | NODE_ENV: 'production', 7 | }, 8 | }; 9 | 10 | export { produce } from 'immer/dist/immer.esm.js'; 11 | // TODO: object freeze 12 | -------------------------------------------------------------------------------- /src/assets/start-failed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/running.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-html-frame/cc-html-frame.types.d.ts: -------------------------------------------------------------------------------- 1 | export type IframeSandbox = 2 | | 'allow-forms' 3 | | 'allow-modals' 4 | | 'allow-pointer-lock' 5 | | 'allow-popups' 6 | | 'allow-popups-to-escape-sandbox' 7 | | 'allow-same-origin' 8 | | 'allow-scripts' 9 | | 'allow-top-navigation'; 10 | -------------------------------------------------------------------------------- /src/stories/assets/oracle.svg: -------------------------------------------------------------------------------- 1 | Oracle -------------------------------------------------------------------------------- /src/components/cc-addon-credentials/cc-addon-credentials.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Credential { 2 | type: 'auth-token' | 'host' | 'password' | 'url' | 'user' | 'port'; 3 | value: string; 4 | secret: boolean; 5 | } 6 | 7 | export type AddonType = 'apm' | 'elasticsearch' | 'kibana' | 'pulsar' | 'materia-kv'; 8 | -------------------------------------------------------------------------------- /src/components/cc-pricing-page/cc-pricing-page.types.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CountablePlanWithQuantity, 3 | RuntimePlanWithQuantity, 4 | } from '../cc-pricing-estimation/cc-pricing-estimation.types.js'; 5 | 6 | export interface SelectedPlansById { 7 | [planId: string]: RuntimePlanWithQuantity | CountablePlanWithQuantity; 8 | } 9 | -------------------------------------------------------------------------------- /test/smart/smart-manager.test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/mail-star-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/accessibility.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | // language=CSS 4 | export const accessibilityStyles = css` 5 | .visually-hidden { 6 | clip: rect(0 0 0 0); 7 | clip-path: inset(50%); 8 | height: 1px; 9 | overflow: hidden; 10 | position: absolute; 11 | white-space: nowrap; 12 | width: 1px; 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /src/components/cc-toast/cc-toast.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a toast is dismissed. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcToastDismissEvent extends CcEvent { 8 | static TYPE = 'cc-toast-dismiss'; 9 | 10 | constructor() { 11 | super(CcToastDismissEvent.TYPE); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/cc-notice/cc-notice.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a notice has been dismissed. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcNoticeDismissEvent extends CcEvent { 8 | static TYPE = 'cc-notice-dismiss'; 9 | 10 | constructor() { 11 | super(CcNoticeDismissEvent.TYPE); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/restarting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-zone/cc-zone.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Zone } from '../common.types.js'; 2 | 3 | export type ZoneState = ZoneStateLoaded | ZoneStateLoading; 4 | 5 | export interface ZoneStateLoaded extends Zone { 6 | type: 'loaded'; 7 | } 8 | 9 | export interface ZoneStateLoading { 10 | type: 'loading'; 11 | } 12 | 13 | export type ZoneModeType = 'default' | 'small' | 'small-infra'; 14 | -------------------------------------------------------------------------------- /src/assets/restart-failed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-invoice-table/cc-invoice-table.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Invoice } from '../common.types.js'; 2 | 3 | export type InvoiceTableState = InvoiceTableStateLoaded | InvoiceTableStateLoading; 4 | 5 | export interface InvoiceTableStateLoaded { 6 | type: 'loaded'; 7 | invoices: Invoice[]; 8 | } 9 | 10 | export interface InvoiceTableStateLoading { 11 | type: 'loading'; 12 | } 13 | -------------------------------------------------------------------------------- /src/stories/assets/ovh.svg: -------------------------------------------------------------------------------- 1 | OVH -------------------------------------------------------------------------------- /src/components/cc-order-summary/cc-order-summary.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a product creation has be requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcProductCreateEvent extends CcEvent { 8 | static TYPE = 'cc-product-create'; 9 | 10 | constructor() { 11 | super(CcProductCreateEvent.TYPE); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/baseline-widely.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/lib/send-to-api.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from './events.js'; 2 | 3 | /** 4 | * Dispatched when an API request failed. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcApiErrorEvent extends CcEvent { 8 | static TYPE = 'cc-api-error'; 9 | 10 | /** 11 | * @param {any} detail 12 | */ 13 | constructor(detail) { 14 | super(CcApiErrorEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/tokens.types.d.ts: -------------------------------------------------------------------------------- 1 | export type ExpirationWarningThresholds = Array<{ 2 | /** Number of days representing the maximum lifetime for which the given threshold is applicable */ 3 | maxApplicableTokenLifetimeInDays: number; 4 | /** Number of days below which the token is considered close to expiration (must be lower than maxApplicableTokenLifetimeInDays) */ 5 | warningThresholdInDays: number; 6 | }>; 7 | -------------------------------------------------------------------------------- /test/helpers/global-mock-date.js: -------------------------------------------------------------------------------- 1 | const fakeNow = new Date('2023-10-26T12:00:00.000Z').getTime(); 2 | window.Date = class extends Date { 3 | static now() { 4 | return fakeNow; 5 | } 6 | 7 | // @ts-ignore 8 | constructor(...options) { 9 | if (options.length > 0) { 10 | // @ts-ignore 11 | super(...options); 12 | } else { 13 | super(fakeNow); 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/stories/assets/italic.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/lib/assets-url.js: -------------------------------------------------------------------------------- 1 | let assetsUrl = 'https://assets.clever-cloud.com'; 2 | 3 | /** @param {string} value */ 4 | export function setAssetsBaseUrl(value) { 5 | assetsUrl = value.replace(/\/$/, ''); 6 | } 7 | 8 | /** 9 | * @param {string} path 10 | * @returns {string} 11 | */ 12 | export function getAssetUrl(path) { 13 | return path.startsWith('/') ? `${assetsUrl}${path}` : `${assetsUrl}/${path}`; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/cc-doc-card/cc-doc-card.types.d.ts: -------------------------------------------------------------------------------- 1 | export type DocCardState = DocCardStateLoaded | DocCardStateLoading; 2 | 3 | export interface DocCardStateLoaded extends DocCard { 4 | type: 'loaded'; 5 | } 6 | 7 | export interface DocCardStateLoading { 8 | type: 'loading'; 9 | } 10 | 11 | export interface DocCard { 12 | description: string; 13 | heading: string; 14 | icons: string[]; 15 | link: string; 16 | } 17 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # run `git config blame.ignoreRevsFile .git-blame-ignore-revs` locally to apply 2 | 3 | # chore: format all remaining files with the format command 4 | a166f4f8da4902a37bf7ef9654186c9b6ef442c4 5 | 6 | # chore: update source code with Prettier and Stylelint update 7 | f9fcd572efca5301e44ae348ec5eb2c41b37e931 8 | 9 | # refactor: apply the new project file structure 10 | 79f7a4893dfb5194a2fb06d781c67766a5aa9349 11 | -------------------------------------------------------------------------------- /docs/copywriting/coming-soon.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '🖋 Copywriting' 3 | title: 'Coming soon...' 4 | --- 5 | 6 | # 🖋 Copywriting 7 | 8 | 9 | 10 | This section will cover how our components talk to end users through written messages. 11 | 12 | Expected soon: 13 | 14 | - our copywriting principles, 15 | - guidelines about tone, internationalisation and a dictionary. -------------------------------------------------------------------------------- /eslint/lit-a11y/eslint-config-lit-a11y-clever-cloud.js: -------------------------------------------------------------------------------- 1 | import eslintLitA11yPlugin from 'eslint-plugin-lit-a11y'; 2 | 3 | export default { 4 | name: 'lit-a11y-cc', 5 | files: ['**/*.js'], 6 | plugins: { 7 | 'lit-a11y': eslintLitA11yPlugin, 8 | }, 9 | rules: { 10 | ...eslintLitA11yPlugin.configs.recommended.rules, 11 | // redundant role may be necessary sometimes 12 | 'lit-a11y/no-redundant-role': 'warn', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/cc-input-text/cc-input-text.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when tags have changed. 5 | * @extends {CcEvent>} 6 | */ 7 | export class CcTagsChangeEvent extends CcEvent { 8 | static TYPE = 'cc-tags-change'; 9 | 10 | /** 11 | * @param {Array} detail 12 | */ 13 | constructor(detail) { 14 | super(CcTagsChangeEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/stories/lib/dom.js: -------------------------------------------------------------------------------- 1 | export function createContainer(elements) { 2 | const frag = document.createDocumentFragment(); 3 | elements.forEach((el) => { 4 | if (typeof el === 'string') { 5 | const title = document.createElement('div'); 6 | title.classList.add('title'); 7 | title.innerHTML = el; 8 | frag.appendChild(title); 9 | } else { 10 | frag.appendChild(el); 11 | } 12 | }); 13 | return frag; 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/waiting.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | // language=CSS 4 | export const waitingStyles = css` 5 | @keyframes waiting { 6 | from { 7 | opacity: 0.85; 8 | } 9 | 10 | to { 11 | opacity: 1; 12 | } 13 | } 14 | 15 | .cc-waiting { 16 | animation-direction: alternate; 17 | animation-duration: 500ms; 18 | animation-iteration-count: infinite; 19 | animation-name: waiting; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /.github/scripts/get-stories-groups.js: -------------------------------------------------------------------------------- 1 | import { appendFileSync } from 'node:fs'; 2 | import { getStoriesGroups } from '../../web-test-runner/get-story-files-groups.js'; 3 | 4 | const storiesGroups = await getStoriesGroups(); 5 | const storiesGroupsNames = storiesGroups.map(({ name }) => name); 6 | 7 | if (process.env.GITHUB_OUTPUT != null) { 8 | appendFileSync(process.env.GITHUB_OUTPUT, `stories-groups=${JSON.stringify(storiesGroupsNames)}\n`); 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/starting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-doc-list/cc-doc-list.types.d.ts: -------------------------------------------------------------------------------- 1 | import { DocCard } from '../cc-doc-card/cc-doc-card.types.js'; 2 | 3 | export type DocListState = DocListStateLoaded | DocListStateLoading | DocListStateError; 4 | 5 | export interface DocListStateLoaded { 6 | type: 'loaded'; 7 | docs: DocCard[]; 8 | } 9 | 10 | export interface DocListStateLoading { 11 | type: 'loading'; 12 | } 13 | 14 | export interface DocListStateError { 15 | type: 'error'; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-input-date/cc-input-date.types.d.ts: -------------------------------------------------------------------------------- 1 | export type InputDateValueState = InputDateValueStateEmpty | InputDateValueStateNaD | InputDateValueStateValid; 2 | 3 | export interface InputDateValueStateEmpty { 4 | type: 'empty'; 5 | } 6 | 7 | export interface InputDateValueStateNaD { 8 | type: 'NaD'; 9 | value: string; 10 | } 11 | 12 | export interface InputDateValueStateValid { 13 | type: 'valid'; 14 | value: string; 15 | date: Date; 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/events.types.d.ts: -------------------------------------------------------------------------------- 1 | export type EventWithTarget< 2 | T extends EventTarget = HTMLElement | SVGElement, 3 | U extends EventTarget = null | HTMLElement | SVGElement, 4 | > = GenericEventWithTarget; 5 | 6 | export type GenericEventWithTarget< 7 | E extends Event, 8 | T extends EventTarget = HTMLElement | SVGElement, 9 | U extends EventTarget = null | HTMLElement | SVGElement, 10 | > = E & { 11 | target: T; 12 | currentTarget: U; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/cc-addon-option/cc-addon-option.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when an add-on option is toggled. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcAddonOptionChangeEvent extends CcEvent { 8 | static TYPE = 'cc-addon-option-change'; 9 | 10 | /** 11 | * @param {boolean} detail 12 | */ 13 | constructor(detail) { 14 | super(CcAddonOptionChangeEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-product-list/cc-product-list.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | description: string; 3 | iconUrl: string; 4 | productStatus?: string; 5 | searchTerms?: string[]; 6 | name: string; 7 | url: string; 8 | } 9 | 10 | export interface ProductsCategory { 11 | categoryName: string; 12 | icon?: string; 13 | products: Product[]; 14 | } 15 | 16 | export interface CategoryFilter { 17 | categoryName: string; 18 | toggled: boolean; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/cc-article-card/cc-article-card.types.d.ts: -------------------------------------------------------------------------------- 1 | export type ArticleCardState = ArticleCardStateLoaded | ArticleCardStateLoading; 2 | 3 | export interface ArticleCardStateLoaded extends ArticleCard { 4 | type: 'loaded'; 5 | } 6 | 7 | export interface ArticleCardStateLoading { 8 | type: 'loading'; 9 | } 10 | 11 | export interface ArticleCard { 12 | banner: string; 13 | date: string; 14 | description: string; 15 | link: string; 16 | title: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/translations/translation.js: -------------------------------------------------------------------------------- 1 | import { i18n as i18nRaw } from '../lib/i18n/i18n.js'; 2 | 3 | // FIXME: We're using `@typedef` instead of `@import` here due to a false positive from TS 4 | // See: https://github.com/microsoft/TypeScript/issues/60908/ 5 | /** 6 | * @typedef {import('./translation.types.js').I18nFunction} I18nFunction 7 | */ 8 | 9 | /** @type {I18nFunction} */ 10 | export function i18n(key, data) { 11 | // @ts-ignore 12 | return i18nRaw(key, data); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/cc-addon-info/cc-addon-info.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when an add-on version update was requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcAddonVersionChangeEvent extends CcEvent { 8 | static TYPE = 'cc-addon-version-change'; 9 | 10 | /** @param {string} targetVersion */ 11 | constructor(targetVersion) { 12 | super(CcAddonVersionChangeEvent.TYPE, targetVersion); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/css-custom-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get an object literally with CSS custom properties and their values 3 | * @param {HTMLElement} element 4 | * @returns {any} 5 | */ 6 | export function getCssCustomProperties(element) { 7 | const styles = getComputedStyle(element); 8 | const entries = Object.values(styles) 9 | .filter((name) => name.startsWith('--')) 10 | .map((name) => [name, styles.getPropertyValue(name)]); 11 | return Object.fromEntries(entries); 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/arrows-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/arrows-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-invoice-list/cc-invoice-list.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Invoice } from '../common.types.js'; 2 | 3 | export type InvoiceListState = InvoiceListStateLoading | InvoiceListStateError | InvoiceListStateLoaded; 4 | 5 | export interface InvoiceListStateLoading { 6 | type: 'loading'; 7 | } 8 | 9 | export interface InvoiceListStateError { 10 | type: 'error'; 11 | } 12 | 13 | export interface InvoiceListStateLoaded { 14 | type: 'loaded'; 15 | invoices: Array; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-matomo-info/cc-matomo-info.types.d.ts: -------------------------------------------------------------------------------- 1 | export type MatomoInfoState = MatomoInfoStateLoaded | MatomoInfoStateLoading | MatomoInfoStateError; 2 | 3 | export interface MatomoInfoStateLoaded { 4 | type: 'loaded'; 5 | matomoUrl: string; 6 | mysqlUrl: string; 7 | phpUrl: string; 8 | redisUrl: string; 9 | } 10 | 11 | export interface MatomoInfoStateLoading { 12 | type: 'loading'; 13 | } 14 | 15 | export interface MatomoInfoStateError { 16 | type: 'error'; 17 | } 18 | -------------------------------------------------------------------------------- /.githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protected_branch='master' 4 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 5 | 6 | if [ "$protected_branch" = "$current_branch" ] 7 | then 8 | read -p "You're about to push to ${protected_branch} branch, is that what you intended? [y|n] " -n 1 -r < /dev/tty 9 | echo 10 | if echo "$REPLY" | grep -E '^[Yy]$' > /dev/null 11 | then 12 | exit 0 13 | fi 14 | exit 1 15 | else 16 | exit 0 17 | fi 18 | -------------------------------------------------------------------------------- /rollup/rollup-cdn.config.js: -------------------------------------------------------------------------------- 1 | import { toCdnEntryName } from '../tasks/cdn-entry-name.js'; 2 | import { CDN_ENVIRONMENTS } from '../tasks/cdn-environments.js'; 3 | import { getCdnRollupConfig } from './rollup-cdn-common.js'; 4 | 5 | const version = process.env.VERSION; 6 | if (version == null) { 7 | throw new Error('VERSION env var is required.'); 8 | } 9 | const cdnEntryName = toCdnEntryName(CDN_ENVIRONMENTS.release, version); 10 | 11 | export default getCdnRollupConfig(cdnEntryName, true); 12 | -------------------------------------------------------------------------------- /src/components/cc-pricing-header/cc-pricing-header.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Zone } from '../common.types.js'; 2 | 3 | export interface PricingHeaderStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | export interface PricingHeaderStateError { 8 | type: 'error'; 9 | } 10 | 11 | export interface PricingHeaderStateLoaded { 12 | type: 'loaded'; 13 | zones: Zone[]; 14 | } 15 | 16 | export type PricingHeaderState = PricingHeaderStateLoading | PricingHeaderStateError | PricingHeaderStateLoaded; 17 | -------------------------------------------------------------------------------- /src/components/cc-header-orga/cc-header-orga.types.d.ts: -------------------------------------------------------------------------------- 1 | export type HeaderOrgaState = HeaderOrgaStateLoaded | HeaderOrgaStateLoading | HeaderOrgaStateError; 2 | 3 | export interface HeaderOrgaStateLoaded { 4 | type: 'loaded'; 5 | name: string; 6 | avatar?: string; 7 | cleverEnterprise?: boolean; 8 | emergencyNumber?: string; 9 | } 10 | 11 | export interface HeaderOrgaStateLoading { 12 | type: 'loading'; 13 | } 14 | 15 | export interface HeaderOrgaStateError { 16 | type: 'error'; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/cc-kv-string-editor/cc-kv-string-editor.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a KV string key modification is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcKvStringValueUpdateEvent extends CcEvent { 8 | static TYPE = 'cc-kv-string-value-update'; 9 | 10 | /** 11 | * @param {string} detail 12 | */ 13 | constructor(detail) { 14 | super(CcKvStringValueUpdateEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/ansi/ansi-palette-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('./ansi.types.js').AnsiPalette} palette - The palette 3 | * @return {string} - The CSS style corresponding to the given palette. 4 | */ 5 | export function ansiPaletteStyle(palette) { 6 | return [ 7 | ...Object.entries(palette).map(([colorName, color]) => `--cc-color-ansi-${colorName}: ${color}`), 8 | 'color: var(--cc-color-ansi-foreground)', 9 | 'background-color: var(--cc-color-ansi-background);', 10 | ].join(';'); 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/cli-commands.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | // language=CSS 4 | export const cliCommandsStyles = css` 5 | cc-block-details [slot='content'] p { 6 | margin: 0; 7 | } 8 | 9 | cc-block-details [slot='content'] dl, 10 | cc-block-details [slot='content'] dt, 11 | cc-block-details [slot='content'] dd { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | cc-block-details [slot='content'] dt { 17 | font-weight: bold; 18 | margin: 1em 0 0.5em; 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /src/lib/form/validation.types.d.ts: -------------------------------------------------------------------------------- 1 | export type Validity = ValidValidity | InvalidValidity; 2 | 3 | export interface ValidValidity { 4 | valid: true; 5 | } 6 | 7 | export interface InvalidValidity { 8 | valid: false; 9 | code: string; 10 | } 11 | 12 | export interface Validator { 13 | validate(value: any, formData?: Object): Validity; 14 | } 15 | 16 | export type ErrorMessage = null | string | Node; 17 | 18 | export type ErrorMessageMap = { [code: string]: ErrorMessage | (() => ErrorMessage) }; 19 | -------------------------------------------------------------------------------- /src/assets/baseline-limited.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/cc-env-var-create/cc-env-var-create.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when an env var creation is requested. 5 | * @extends {CcEvent<{name: string, value: string}>} 6 | */ 7 | export class CcEnvVarCreateEvent extends CcEvent { 8 | static TYPE = 'cc-env-var-create'; 9 | 10 | /** 11 | * @param {{name: string, value: string}} detail 12 | */ 13 | constructor(detail) { 14 | super(CcEnvVarCreateEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/notifications.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from './events.js'; 2 | 3 | /** 4 | * @import { Notification } from '../components/common.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when a notification message is raised. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcNotifyEvent extends CcEvent { 12 | static TYPE = 'cc-notify'; 13 | 14 | /** 15 | * @param {Notification} detail 16 | */ 17 | constructor(detail) { 18 | super(CcNotifyEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/cc-article-list/cc-article-list.types.d.ts: -------------------------------------------------------------------------------- 1 | import { ArticleCard } from '../cc-article-card/cc-article-card.types.js'; 2 | 3 | export type ArticleListState = ArticleListStateLoaded | ArticleListStateLoading | ArticleListStateError; 4 | 5 | export interface ArticleListStateLoaded { 6 | type: 'loaded'; 7 | articles: ArticleCard[]; 8 | } 9 | 10 | export interface ArticleListStateLoading { 11 | type: 'loading'; 12 | } 13 | 14 | export interface ArticleListStateError { 15 | type: 'error'; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-tile-scalability/cc-tile-scalability.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Scalability } from '../common.types.js'; 2 | 3 | export type TileScalabilityState = TileScalabilityStateLoaded | TileScalabilityStateLoading | TileScalabilityStateError; 4 | 5 | export interface TileScalabilityStateLoaded extends Scalability { 6 | type: 'loaded'; 7 | } 8 | 9 | export interface TileScalabilityStateLoading { 10 | type: 'loading'; 11 | } 12 | 13 | export interface TileScalabilityStateError { 14 | type: 'error'; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/cc-kv-string-editor/cc-kv-string-editor.types.d.ts: -------------------------------------------------------------------------------- 1 | export type CcKvKeyStringEditorState = 2 | | CcKvKeyStringEditorStateLoading 3 | | CcKvKeyStringEditorStateIdle 4 | | CcKvKeyStringEditorStateSaving; 5 | 6 | export interface CcKvKeyStringEditorStateLoading { 7 | type: 'loading'; 8 | } 9 | 10 | export interface CcKvKeyStringEditorStateIdle { 11 | type: 'idle'; 12 | value: string; 13 | } 14 | 15 | export interface CcKvKeyStringEditorStateSaving { 16 | type: 'saving'; 17 | value: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/cc-logs-instances/cc-logs-instances.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when logs instances selection changes. 5 | * @extends {CcEvent>} 6 | */ 7 | export class CcLogsInstancesSelectionChangeEvent extends CcEvent { 8 | static TYPE = 'cc-logs-instances-selection-change'; 9 | 10 | /** 11 | * @param {Array} detail 12 | */ 13 | constructor(detail) { 14 | super(CcLogsInstancesSelectionChangeEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-map/cc-map.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Marker, PointExpression } from 'src/lib/leaflet/leaflet-esm.js'; 2 | import { Point } from '../common.types.js'; 3 | 4 | export interface CachedPoint { 5 | point: Point; 6 | marker: Marker & { 7 | tag?: string; 8 | tooltip?: string | { tag: string }; 9 | }; 10 | iconElement: MapIconElement; 11 | } 12 | 13 | export interface MapIconElement extends HTMLElement { 14 | size: PointExpression; 15 | anchor: PointExpression; 16 | tooltip: PointExpression; 17 | } 18 | -------------------------------------------------------------------------------- /src/stories/lib/timers.js: -------------------------------------------------------------------------------- 1 | export function setTimeoutDom(fn, delay, domNode) { 2 | const id = setTimeout(() => { 3 | if (domNode.parentNode == null) { 4 | clearTimeout(id); 5 | } else { 6 | fn(); 7 | } 8 | }, delay); 9 | return id; 10 | } 11 | 12 | export function setIntervalDom(fn, delay, domNode) { 13 | const id = setInterval(() => { 14 | if (domNode.parentNode == null) { 15 | clearInterval(id); 16 | } else { 17 | fn(); 18 | } 19 | }, delay); 20 | return id; 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/git.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-order-summary/cc-order-summary.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface OrderSummary { 2 | name: string; 3 | tags?: Array; 4 | logo?: LogoInfos; 5 | configuration?: Array; 6 | submitStatus?: 'disabled' | 'waiting'; 7 | } 8 | 9 | export interface LogoInfos { 10 | url: string; 11 | alt: string; 12 | } 13 | 14 | export interface ConfigurationItem { 15 | label: string; 16 | value: string; 17 | a11yLive?: boolean; 18 | skeleton?: boolean; 19 | skeletonValueOnly?: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/cc-feature-list/cc-feature-list.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a feature setting changes. 5 | * @extends {CcEvent<{featureId: string, newValue: string}>} 6 | */ 7 | export class CcFeatureSettingChangeEvent extends CcEvent { 8 | static TYPE = 'cc-feature-setting-change'; 9 | 10 | /** 11 | * @param {{featureId: string, newValue: string}} detail 12 | */ 13 | constructor(detail) { 14 | super(CcFeatureSettingChangeEvent.TYPE, detail); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-logsmap/cc-logsmap.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { MapModeType } from '../common.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the logsmap display mode changes. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcLogsmapModeChangeEvent extends CcEvent { 12 | static TYPE = 'cc-logsmap-mode-change'; 13 | 14 | /** 15 | * @param {MapModeType} detail 16 | */ 17 | constructor(detail) { 18 | super(CcLogsmapModeChangeEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/remote-assets.js: -------------------------------------------------------------------------------- 1 | import { getAssetUrl } from './assets-url.js'; 2 | 3 | /** 4 | * @param {string|null} countryCode 5 | * @return {string|null} 6 | */ 7 | export function getFlagUrl(countryCode) { 8 | return countryCode != null ? getAssetUrl(`/flags/${countryCode.toLowerCase()}.svg`) : null; 9 | } 10 | 11 | /** 12 | * @param {string|null} providerSlug 13 | * @return {string|null} 14 | */ 15 | export function getInfraProviderLogoUrl(providerSlug) { 16 | return providerSlug != null ? getAssetUrl(`/infra/${providerSlug}.svg`) : null; 17 | } 18 | -------------------------------------------------------------------------------- /docs/guidelines/coming-soon.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '📖 Guidelines' 3 | title: 'Coming soon...' 4 | --- 5 | 6 | # 📖 Guidelines 7 | 8 | 9 | 10 | This section will cover how we design our components, from a global perspective but also from a component level. 11 | The guides will mostly be recommendations and best practices and not intangible rules. 12 | 13 | Expected soon: 14 | 15 | - our design principles, 16 | - guidelines about layout, list, form, button, interaction and more! 17 | -------------------------------------------------------------------------------- /rollup/rollup-plugin-styles-assets.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export function stylesAssetsPlugin(options) { 4 | const transform = options?.transform ?? ((css) => css); 5 | return { 6 | generateBundle: async function () { 7 | const stylesheet = fs.readFileSync('src/styles/default-theme.css', 'utf8'); 8 | const minifiedStylesheet = transform(stylesheet); 9 | 10 | this.emitFile({ 11 | type: 'asset', 12 | name: 'default-theme.css', 13 | source: minifiedStylesheet, 14 | }); 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/cc-pricing-product/cc-pricing-product.types.d.ts: -------------------------------------------------------------------------------- 1 | import { FormattedFeature, Plan } from '../common.types.js'; 2 | 3 | interface PricingProductStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | interface PricingProductStateError { 8 | type: 'error'; 9 | } 10 | 11 | interface PricingProductStateLoaded { 12 | type: 'loaded'; 13 | productFeatures: FormattedFeature[]; 14 | name: string; 15 | plans: Plan[]; 16 | } 17 | 18 | export type PricingProductState = PricingProductStateLoading | PricingProductStateError | PricingProductStateLoaded; 19 | -------------------------------------------------------------------------------- /src/components/cc-token-api-creation-form/cc-token-api-creation-form.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { NewToken } from './cc-token-api-creation-form.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when a token creation is requested. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcTokenCreateEvent extends CcEvent { 12 | static TYPE = 'cc-token-create'; 13 | 14 | /** @param {NewToken} detail */ 15 | constructor(detail) { 16 | super(CcTokenCreateEvent.TYPE, detail); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/cc-invoice/cc-invoice.types.d.ts: -------------------------------------------------------------------------------- 1 | export type InvoiceState = InvoiceStateLoading | InvoiceStateError | InvoiceStateLoaded; 2 | 3 | interface InvoiceStateLoading { 4 | type: 'loading'; 5 | number?: string; 6 | } 7 | 8 | interface InvoiceStateError { 9 | type: 'error'; 10 | number: string; 11 | } 12 | 13 | interface InvoiceStateLoaded { 14 | type: 'loaded'; 15 | number: string; 16 | downloadUrl: string; 17 | emissionDate: string; 18 | amount: number; 19 | currency: string; // ISO 4217 currency code 20 | invoiceHtml: string; 21 | } 22 | -------------------------------------------------------------------------------- /rollup/rollup-cdn-preview.config.js: -------------------------------------------------------------------------------- 1 | import { toCdnEntryName } from '../tasks/cdn-entry-name.js'; 2 | import { CDN_ENVIRONMENTS } from '../tasks/cdn-environments.js'; 3 | import { getCurrentBranch } from '../tasks/git-utils.js'; 4 | import { getCdnRollupConfig } from './rollup-cdn-common.js'; 5 | 6 | const nameFromEnv = process.env.PREVIEW; 7 | const name = nameFromEnv == null || nameFromEnv.length === 0 ? getCurrentBranch() : nameFromEnv; 8 | const cdnEntryName = toCdnEntryName(CDN_ENVIRONMENTS.preview, name); 9 | 10 | export default getCdnRollupConfig(cdnEntryName, false); 11 | -------------------------------------------------------------------------------- /rollup/rollup-plugin-index-generator.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export function indexGeneratorPlugin(files) { 4 | return { 5 | name: 'index-generator', 6 | generateBundle() { 7 | const source = files 8 | .map((f) => { 9 | const filename = path.parse(f).base; 10 | return `import './${filename}';`; 11 | }) 12 | .join('\n'); 13 | 14 | this.emitFile({ 15 | type: 'asset', 16 | id: 'index', 17 | fileName: 'index.js', 18 | source, 19 | }); 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/cc-jenkins-info/cc-jenkins-info.types.d.ts: -------------------------------------------------------------------------------- 1 | export type JenkinsInfoState = JenkinsInfoStateLoading | JenkinsInfoStateError | JenkinsInfoStateLoaded; 2 | 3 | interface JenkinsInfoStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | interface JenkinsInfoStateError { 8 | type: 'error'; 9 | } 10 | 11 | interface JenkinsInfoStateLoaded { 12 | type: 'loaded'; 13 | jenkinsLink: string; 14 | jenkinsManageLink: string; 15 | versions: JenkinsInfoVersions; 16 | } 17 | 18 | interface JenkinsInfoVersions { 19 | current: string; 20 | available: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/cc-tile-instances/cc-tile-instances.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TileInstancesState = TileInstancesStateLoading | TileInstancesStateLoaded | TileInstancesStateError; 2 | 3 | export interface TileInstancesStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | export interface TileInstancesStateLoaded { 8 | type: 'loaded'; 9 | running: Array; 10 | deploying: Array; 11 | } 12 | 13 | export interface TileInstancesStateError { 14 | type: 'error'; 15 | } 16 | 17 | export interface InstanceState { 18 | flavorName: string; 19 | count: number; 20 | } 21 | -------------------------------------------------------------------------------- /src/stories/assets/underline.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/cc-addon-option-form/cc-addon-option-form.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { AddonOptionStates } from '../common.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the add-on options form is submitted. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcAddonOptionFormSubmitEvent extends CcEvent { 12 | static TYPE = 'cc-addon-option-form-submit'; 13 | 14 | /** 15 | * @param {AddonOptionStates} detail 16 | */ 17 | constructor(detail) { 18 | super(CcAddonOptionFormSubmitEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/cc-plan-picker/cc-plan-picker.types.d.ts: -------------------------------------------------------------------------------- 1 | import { BadgeIntent } from '../cc-badge/cc-badge.types.js'; 2 | import { IconModel } from '../common.types.js'; 3 | 4 | export interface PlanItem { 5 | id: string; 6 | badge?: PlanBadge; 7 | name: string; 8 | details?: PlanDetails[]; 9 | disabled?: boolean; 10 | selected?: boolean; 11 | relatedPlans?: Array>; 12 | } 13 | 14 | export interface PlanBadge { 15 | content: string; 16 | intent?: BadgeIntent; 17 | } 18 | 19 | export interface PlanDetails { 20 | icon: IconModel; 21 | value: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/cc-tile-deployments/cc-tile-deployments.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TileDeploymentsState = TileDeploymentsStateLoading | TileDeploymentsStateLoaded | TileDeploymentsStateError; 2 | 3 | interface TileDeploymentsStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | interface TileDeploymentsStateLoaded { 8 | type: 'loaded'; 9 | deploymentsInfo: Array; 10 | } 11 | 12 | interface TileDeploymentsStateError { 13 | type: 'error'; 14 | } 15 | 16 | export interface DeploymentTileInfo { 17 | state: string; 18 | action: string; 19 | date: string; 20 | logsUrl: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/cc-logs-control/cc-logs-control.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { LogsOptionsChangeEventData } from './cc-logs-control.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the logs options have changed. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcLogsOptionsChangeEvent extends CcEvent { 12 | static TYPE = 'cc-logs-options-change'; 13 | 14 | /** 15 | * @param {LogsOptionsChangeEventData} detail 16 | */ 17 | constructor(detail) { 18 | super(CcLogsOptionsChangeEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/stories/assets/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.storybook/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | # If an existing asset or directory is requested, serve it 4 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] 5 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d 6 | RewriteRule ^ - [L] 7 | 8 | 9 | 10 | Header set Cache-Control "no-cache" 11 | 12 | 13 | 14 | Header set Cache-Control "max-age=2592000, immutable" 15 | 16 | 17 | 18 | Header set Cache-Control "max-age=31536000, immutable" 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/lib/fake-strings.js: -------------------------------------------------------------------------------- 1 | // It's more representative of a real sentence to use this than to try to generate something with spaces. 2 | const FAKE_STRING_BASE = 3 | '??? ??????????? ???? ?????? ??? ????????????? ????????? ?? ??????? ?? ????? ????????????? ?? ?????? ??? ?? ??????? ?? ????????? ?? ?????????? ?? ??????????? ????? ?????????? ?? ???? ???????????? ???? ????? ????????????????'; 4 | 5 | /** 6 | * Creates a fake string of `charCount` characters 7 | * 8 | * @param {number} charCount 9 | * @return {string} 10 | */ 11 | export function fakeString(charCount) { 12 | return FAKE_STRING_BASE.substring(0, charCount); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/cc-clipboard/cc-clipboard.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-clipboard.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🧬 Atoms/', 7 | component: 'cc-clipboard', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-clipboard', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ value: 'Text to copy' }], 16 | }); 17 | 18 | export const emptyText = makeStory(conf, { 19 | items: [{ value: '' }], 20 | }); 21 | 22 | export const shortText = makeStory(conf, { 23 | items: [{ value: 'Foo' }], 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/cc-zone-picker/cc-zone-picker.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface ZoneItem { 2 | code: string; 3 | country: string; 4 | countryCode: string; 5 | name: string; 6 | flagUrl: string; 7 | images: Array; 8 | disabled?: boolean; 9 | selected?: boolean; 10 | } 11 | 12 | export interface ZoneImage { 13 | url: string | URL; 14 | alt: string; 15 | } 16 | 17 | export type ZonesSections = Array | [SingleZoneSection]; 18 | 19 | export interface ZoneSection { 20 | title: string; 21 | zones: Array; 22 | } 23 | 24 | export interface SingleZoneSection { 25 | zones: Array; 26 | } 27 | -------------------------------------------------------------------------------- /src/stories/lib/autodocs-template.jsx: -------------------------------------------------------------------------------- 1 | // we disable this rule because this file is only meant to be processed by Storybook. It is not part of our npm / CDN bundle. 2 | // eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars 3 | import { Controls, Description, Subtitle, Title } from '@storybook/addon-docs/blocks'; 4 | // eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars 5 | import React from 'react'; 6 | 7 | export function AutodocsTemplate() { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | > 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/cc-logs-message-filter/cc-logs-message-filter.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { LogsMessageFilterValue } from './cc-logs-message-filter.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the logs message filter changes. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcLogsMessageFilterChangeEvent extends CcEvent { 12 | static TYPE = 'cc-logs-message-filter-change'; 13 | 14 | /** 15 | * @param {LogsMessageFilterValue} detail 16 | */ 17 | constructor(detail) { 18 | super(CcLogsMessageFilterChangeEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/focus-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Element|ShadowRoot|Document} element The root element on which the querySelector is made (can be `null`) 3 | * @param {string} selector The query selector 4 | * @param {() => void} [orElse] The optional function to call when the result of the query selector is `null` 5 | */ 6 | export function focusBySelector(element, selector, orElse = null) { 7 | /** @type {Element & { focus?: () => void}} */ 8 | const elementToFocus = element?.querySelector(selector); 9 | if (typeof elementToFocus?.focus === 'function') { 10 | elementToFocus.focus(); 11 | } else { 12 | orElse?.(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/animate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {ShadowRoot} shadowRoot 4 | * @param {string} selector 5 | * @param {Array | PropertyIndexedKeyframes} keyframes 6 | * @param {number | KeyframeAnimationOptions} options 7 | */ 8 | export function animate(shadowRoot, selector, keyframes, options) { 9 | Array.from(shadowRoot.querySelectorAll(selector)).forEach((element) => element.animate(keyframes, options)); 10 | } 11 | 12 | export const QUICK_SHRINK = { 13 | keyframes: [{ transform: 'scale(1)' }, { transform: 'scale(0.9)' }, { transform: 'scale(1)' }], 14 | options: { 15 | duration: 200, 16 | easing: 'ease-in-out', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/cc-addon-header/cc-addon-header.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatch when an addon restart is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcAddonRestartEvent extends CcEvent { 8 | static TYPE = 'cc-addon-restart'; 9 | 10 | constructor() { 11 | super(CcAddonRestartEvent.TYPE); 12 | } 13 | } 14 | 15 | /** 16 | * Dispatch when an addon rebuild is requested. 17 | * @extends {CcEvent} 18 | */ 19 | export class CcAddonRebuildEvent extends CcEvent { 20 | static TYPE = 'cc-addon-rebuild'; 21 | 22 | constructor() { 23 | super(CcAddonRebuildEvent.TYPE); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/cc-header-app/cc-header-app.types.d.ts: -------------------------------------------------------------------------------- 1 | import { App, AppStatus, Zone } from '../common.types.js'; 2 | 3 | export type HeaderAppState = HeaderAppStateLoaded | HeaderAppStateLoading | HeaderAppStateError; 4 | 5 | export interface HeaderAppStateLoaded extends App { 6 | type: 'loaded'; 7 | status: AppStatus; 8 | runningCommit?: string | null; 9 | startingCommit?: string | null; 10 | zone: Zone; 11 | } 12 | 13 | export interface HeaderAppStateLoading { 14 | type: 'loading'; 15 | } 16 | 17 | export interface HeaderAppStateError { 18 | type: 'error'; 19 | } 20 | 21 | export type LastUserAction = 'start' | 'restart' | 'cancel' | 'stop'; 22 | -------------------------------------------------------------------------------- /src/components/cc-addon-credentials-content/cc-addon-credentials-content.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a network group creation is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcNgEnable extends CcEvent { 8 | static TYPE = 'cc-ng-enable'; 9 | 10 | constructor() { 11 | super(CcNgEnable.TYPE); 12 | } 13 | } 14 | 15 | /** 16 | * Dispatched when a network group deletion is requested. 17 | * @extends {CcEvent} 18 | */ 19 | export class CcNgDisable extends CcEvent { 20 | static TYPE = 'cc-ng-disable'; 21 | 22 | constructor() { 23 | super(CcNgDisable.TYPE); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/send-to-api.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface ApiConfig { 2 | API_HOST: string; 3 | API_OAUTH_TOKEN: string; 4 | API_OAUTH_TOKEN_SECRET: string; 5 | OAUTH_CONSUMER_KEY: string; 6 | OAUTH_CONSUMER_SECRET: string; 7 | } 8 | 9 | export interface Warp10ApiConfig { 10 | WARP_10_HOST: string; 11 | API_OAUTH_TOKEN: string; 12 | API_OAUTH_TOKEN_SECRET: string; 13 | OAUTH_CONSUMER_KEY: string; 14 | OAUTH_CONSUMER_SECRET: string; 15 | } 16 | 17 | export interface AuthBridgeConfig { 18 | AUTH_BRIDGE_HOST: string; 19 | API_OAUTH_TOKEN: string; 20 | API_OAUTH_TOKEN_SECRET: string; 21 | OAUTH_CONSUMER_KEY: string; 22 | OAUTH_CONSUMER_SECRET: string; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/cc-toaster/cc-toaster.types.d.ts: -------------------------------------------------------------------------------- 1 | import { NotificationIntent } from '../common.types.js'; 2 | 3 | export type ToastPosition = 'top' | 'top-left' | 'top-right' | 'bottom' | 'bottom-left' | 'bottom-right'; 4 | 5 | export type ToastAnimation = 'fade' | 'slide' | 'fade-and-slide'; 6 | 7 | export interface Notification { 8 | message: string; 9 | title?: string; 10 | intent: NotificationIntent; 11 | options?: ToastOptions; 12 | } 13 | 14 | export interface ToastOptions { 15 | timeout?: number; 16 | closeable?: boolean; 17 | showProgress?: boolean; 18 | } 19 | 20 | export interface Toast extends Notification { 21 | key: string; 22 | dismissing?: boolean; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/cc-addon-features/cc-addon-features.types.d.ts: -------------------------------------------------------------------------------- 1 | import { IconModel } from '../common.types.js'; 2 | 3 | export type AddonFeaturesState = AddonFeaturesStateLoaded | AddonFeaturesStateLoading | AddonFeaturesStateError; 4 | 5 | export interface AddonFeature { 6 | name: string; 7 | value: string; 8 | } 9 | 10 | export interface AddonFeaturesStateLoaded { 11 | type: 'loaded'; 12 | features: AddonFeature[]; 13 | } 14 | 15 | export interface AddonFeaturesStateLoading { 16 | type: 'loading'; 17 | } 18 | 19 | export interface AddonFeaturesStateError { 20 | type: 'error'; 21 | } 22 | 23 | export interface AddonFeatureWithIcon extends AddonFeature { 24 | icon?: IconModel; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/cc-addon-linked-apps/cc-addon-linked-apps.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Zone } from '../common.types.js'; 2 | 3 | export type AddonLinkedAppsState = AddonLinkedAppsStateLoading | AddonLinkedAppsStateLoaded | AddonLinkedAppsStateError; 4 | 5 | export interface AddonLinkedAppsStateLoading { 6 | type: 'loading'; 7 | } 8 | 9 | export interface AddonLinkedAppsStateLoaded { 10 | type: 'loaded'; 11 | linkedApplications: Array; 12 | } 13 | 14 | export interface AddonLinkedAppsStateError { 15 | type: 'error'; 16 | } 17 | 18 | export interface LinkedApplication { 19 | name: string; 20 | link: string; 21 | variantName: string; 22 | variantLogoUrl: string; 23 | zone: Zone; 24 | } 25 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/cc-logs-date-range-selector/cc-logs-date-range-selector.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { LogsDateRangeSelectionChangeEventData } from './cc-logs-date-range-selector.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the logs date range selection changes. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcLogsDateRangeSelectionChangeEvent extends CcEvent { 12 | static TYPE = 'cc-logs-date-range-selection-change'; 13 | 14 | /** 15 | * @param {LogsDateRangeSelectionChangeEventData} detail 16 | */ 17 | constructor(detail) { 18 | super(CcLogsDateRangeSelectionChangeEvent.TYPE, detail); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/i18n/i18n.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TranslationsMap = { [lang: string]: Translations }; 2 | 3 | export type Translations = { [key: string]: Translation }; 4 | 5 | export type Translation = string | TranslateFunction; 6 | 7 | export type TranslateFunction = (data: any) => Translated; 8 | 9 | export type Translated = string | Node; 10 | 11 | export type DateInput = number | string | Date; 12 | 13 | export type DateFormatter = (dateInput: DateInput) => string; 14 | 15 | export type DateUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'; 16 | 17 | export type RelativeTimeFormatFunction = (value: number, unit: DateUnit) => string; 18 | 19 | export type NumberFormatter = (number: number) => string; 20 | -------------------------------------------------------------------------------- /src/lib/date/date.types.d.ts: -------------------------------------------------------------------------------- 1 | export type Timezone = 'local' | 'UTC'; 2 | 3 | export type DateFormat = 'datetime-iso' | 'datetime-short'; 4 | 5 | export interface DateFormattedParts { 6 | date: string; 7 | separator: 'T' | ' '; 8 | time: string; 9 | millisecond?: string; 10 | timezone?: string; 11 | } 12 | 13 | export type DateFormattedPart = keyof DateFormattedParts; 14 | 15 | export interface DateParts { 16 | year: string; 17 | month: string; 18 | day: string; 19 | hour: string; 20 | minute: string; 21 | second: string; 22 | millisecond?: string; 23 | timezone?: string; 24 | } 25 | 26 | export type DatePart = keyof DateParts; 27 | 28 | export type DateField = 'Y' | 'M' | 'D' | 'H' | 'm' | 's' | 'S'; 29 | -------------------------------------------------------------------------------- /src/lib/i18n/i18n-display.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the translated name of a country using its code with a fallback 3 | * 4 | * @param {string} lang - BCP 47 language tag 5 | * @param {string} code - ISO 3166 Country code 6 | * @param {string} name - Country name (fallback) 7 | * @returns {string} 8 | */ 9 | export function getCountryName(lang, code, name) { 10 | // try/catch with fallback on english name because the support is not great for now 11 | // https://caniuse.com/mdn-javascript_builtins_intl_displaynames_of 12 | try { 13 | // This API was not really designed for this but... 14 | return new Intl.DisplayNames([lang], { type: 'region' }).of(code.toUpperCase()); 15 | } catch { 16 | return name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/cc-heptapod-info/cc-heptapod-info.types.d.ts: -------------------------------------------------------------------------------- 1 | export type HeptapodInfoState = 2 | | HeptapodInfoStateLoaded 3 | | HeptapodInfoStateLoading 4 | | HeptapodInfoStateError 5 | | HeptapodInfoStateNotUsed; 6 | 7 | export interface HeptapodInfoStateLoaded { 8 | type: 'loaded'; 9 | statistics: Statistics; 10 | } 11 | 12 | export interface HeptapodInfoStateLoading { 13 | type: 'loading'; 14 | } 15 | 16 | export interface HeptapodInfoStateError { 17 | type: 'error'; 18 | } 19 | 20 | export interface HeptapodInfoStateNotUsed { 21 | type: 'not-used'; 22 | } 23 | 24 | export interface Statistics { 25 | privateActiveUsers: number; 26 | publicActiveUsers: number; 27 | storage: number; 28 | price: number; 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/ansi/ansi.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface AnsiPalette { 2 | foreground: string; 3 | background: string; 4 | 'background-hover': string; 5 | 'background-selected': string; 6 | black: string; 7 | red: string; 8 | green: string; 9 | yellow: string; 10 | blue: string; 11 | magenta: string; 12 | cyan: string; 13 | white: string; 14 | 'bright-black': string; 15 | 'bright-red': string; 16 | 'bright-green': string; 17 | 'bright-yellow': string; 18 | 'bright-blue': string; 19 | 'bright-magenta': string; 20 | 'bright-cyan': string; 21 | 'bright-white': string; 22 | } 23 | 24 | export interface AnsiPart { 25 | styles: Array; 26 | text: string; 27 | } 28 | 29 | export type ColorName = keyof AnsiPalette; 30 | -------------------------------------------------------------------------------- /src/stories/assets/scaleway.svg: -------------------------------------------------------------------------------- 1 | Scaleway -------------------------------------------------------------------------------- /test/helpers/mock-now.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} nowIsoString The fake now ISO string 3 | * @param {function} fn the function to execute 4 | */ 5 | export function withMockedNow(nowIsoString, fn) { 6 | const realDate = window.Date; 7 | const fakeNow = new Date(nowIsoString).getTime(); 8 | // @ts-ignore 9 | window.Date = class extends Date { 10 | static now() { 11 | return fakeNow; 12 | } 13 | 14 | // @ts-ignore 15 | constructor(...options) { 16 | if (options.length > 0) { 17 | // @ts-ignore 18 | super(...options); 19 | } else { 20 | super(fakeNow); 21 | } 22 | } 23 | }; 24 | try { 25 | return fn(); 26 | } finally { 27 | window.Date = realDate; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/cc-tile-requests/cc-tile-requests.types.d.ts: -------------------------------------------------------------------------------- 1 | export type RequestsData = [ 2 | number, // Start timestamp in milliseconds. Expected to be rounded to the hour of its respective TZ. 3 | number, // End timestamp in milliseconds. Expected to be rounded to the hour of its respective TZ. 4 | number, // Number of request during this time window. 5 | ]; 6 | 7 | export type TileRequestsState = TileRequestsStateLoaded | TileRequestsStateLoading | TileRequestsStateError; 8 | 9 | export interface TileRequestsStateLoaded { 10 | type: 'loaded'; 11 | data: Array; 12 | } 13 | 14 | export interface TileRequestsStateLoading { 15 | type: 'loading'; 16 | } 17 | 18 | export interface TileRequestsStateError { 19 | type: 'error'; 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/hyoob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#FFFFFF', 8 | background: '#000000', 9 | 'background-hover': '#1E1E1E', 10 | 'background-selected': '#2B2B2B', 11 | black: '#000000', 12 | red: '#FF4040', 13 | green: '#90AF7D', 14 | yellow: '#FFFF66', 15 | blue: '#9999FF', 16 | magenta: '#FF4BB9', 17 | cyan: '#3AEEE0', 18 | white: '#FFFFFF', 19 | 'bright-black': '#BBBBBB', 20 | 'bright-red': '#FF4040', 21 | 'bright-green': '#90AF7D', 22 | 'bright-yellow': '#FFFF66', 23 | 'bright-blue': '#9999FF', 24 | 'bright-magenta': '#FF4BB9', 25 | 'bright-cyan': '#3AEEE0', 26 | 'bright-white': '#FFFFFF', 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/regex-parse.js: -------------------------------------------------------------------------------- 1 | const VALID_FLAGS = 'dgimsuy'; 2 | const REGEX_WITH_TRAILING_SLASH = /\/(?.+)\/(?[a-z]*)/i; 3 | 4 | /** 5 | * @param {string} input 6 | * @return {RegExp} 7 | */ 8 | export function parseRegex(input) { 9 | if (input.startsWith('/')) { 10 | const m = input.match(REGEX_WITH_TRAILING_SLASH); 11 | 12 | if (m == null) { 13 | throw new Error('invalid regular expression format'); 14 | } 15 | 16 | const { expression, flags } = m.groups; 17 | 18 | const validFlags = Array.from(new Set(flags)) 19 | .filter((flag) => VALID_FLAGS.includes(flag)) 20 | .join(''); 21 | 22 | return new RegExp(expression, validFlags); 23 | } else { 24 | return new RegExp(input); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/cc-addon-mysql-options/cc-addon-mysql-options.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-addon-mysql-options.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Addon/', 7 | component: 'cc-addon-mysql-options', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-addon-mysql-options', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ options: [{ name: 'encryption', enabled: false }] }], 16 | }); 17 | 18 | export const encryptionEnabled = makeStory(conf, { 19 | items: [{ options: [{ name: 'encryption', enabled: true }] }], 20 | }); 21 | 22 | // This component isn't used when there are no options => no story for this case. 23 | -------------------------------------------------------------------------------- /src/components/cc-addon-redis-options/cc-addon-redis-options.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-addon-redis-options.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Addon/', 7 | component: 'cc-addon-redis-options', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-addon-redis-options', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ options: [{ name: 'encryption', enabled: false }] }], 16 | }); 17 | 18 | export const encryptionEnabled = makeStory(conf, { 19 | items: [{ options: [{ name: 'encryption', enabled: true }] }], 20 | }); 21 | 22 | // This component isn't used when there are no options => no story for this case. 23 | -------------------------------------------------------------------------------- /src/components/cc-visual-tests-report-menu/cc-visual-tests-report-menu.types.d.ts: -------------------------------------------------------------------------------- 1 | import { VisualTestResult } from '../cc-visual-tests-report/visual-tests-report.types.js'; 2 | 3 | export type VisualTestsReportMenuViewportEntry = { 4 | viewportType: VisualTestResult['viewportType']; 5 | browserName: VisualTestResult['browserName']; 6 | id: string; 7 | }; 8 | 9 | export type VisualTestsReportMenuStoryEntry = { 10 | storyName: string; 11 | viewports: Array; 12 | }; 13 | 14 | export type VisualTestsReportMenuComponentEntry = { 15 | componentTagName: string; 16 | stories: Array; 17 | }; 18 | 19 | export type VisualTestsReportMenuEntries = Array; 20 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#000000', 8 | background: '#FFFFFF', 9 | 'background-hover': '#F5F5F5', 10 | 'background-selected': '#EBEBEB', 11 | black: '#000000', 12 | red: '#990000', 13 | green: '#0C7814', 14 | yellow: '#6D6B12', 15 | blue: '#0000B2', 16 | magenta: '#B200B2', 17 | cyan: '#28757B', 18 | white: '#BFBFBF', 19 | 'bright-black': '#666666', 20 | 'bright-red': '#D60000', 21 | 'bright-green': '#00D900', 22 | 'bright-yellow': '#E5E500', 23 | 'bright-blue': '#0000FF', 24 | 'bright-magenta': '#E500E5', 25 | 'bright-cyan': '#00E5E5', 26 | 'bright-white': '#E5E5E5', 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/everblush.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#DADADA', 8 | background: '#141B1E', 9 | 'background-hover': '#1E2528', 10 | 'background-selected': '#282F32', 11 | black: '#232A2D', 12 | red: '#E57474', 13 | green: '#8CCF7E', 14 | yellow: '#E5C76B', 15 | blue: '#67B0E8', 16 | magenta: '#C47FD5', 17 | cyan: '#6CBFBF', 18 | white: '#B3B9B8', 19 | 'bright-black': '#2D3437', 20 | 'bright-red': '#EF7E7E', 21 | 'bright-green': '#96D988', 22 | 'bright-yellow': '#F4D67A', 23 | 'bright-blue': '#71BAF2', 24 | 'bright-magenta': '#CE89DF', 25 | 'bright-cyan': '#67CBE7', 26 | 'bright-white': '#BDC3C2', 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/night-owl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#D6DEEB', 8 | background: '#011627', 9 | 'background-hover': '#0B2031', 10 | 'background-selected': '#152A3B', 11 | black: '#011627', 12 | red: '#EF5350', 13 | green: '#22DA6E', 14 | yellow: '#ADDB67', 15 | blue: '#82AAFF', 16 | magenta: '#C792EA', 17 | cyan: '#21C7A8', 18 | white: '#FFFFFF', 19 | 'bright-black': '#575656', 20 | 'bright-red': '#EF5350', 21 | 'bright-green': '#22DA6E', 22 | 'bright-yellow': '#FFEB95', 23 | 'bright-blue': '#82AAFF', 24 | 'bright-magenta': '#C792EA', 25 | 'bright-cyan': '#7FDBCA', 26 | 'bright-white': '#FFFFFF', 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/one-light.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#2A2B32', 8 | background: '#F8F8F8', 9 | 'background-hover': '#EEEEEE', 10 | 'background-selected': '#E4E4E4', 11 | black: '#000000', 12 | red: '#D13B38', 13 | green: '#3B813D', 14 | yellow: '#855504', 15 | blue: '#315EEE', 16 | magenta: '#930092', 17 | cyan: '#0E6FAD', 18 | white: '#8E8F96', 19 | 'bright-black': '#2A2B32', 20 | 'bright-red': '#D13B38', 21 | 'bright-green': '#3B813D', 22 | 'bright-yellow': '#855504', 23 | 'bright-blue': '#315EEE', 24 | 'bright-magenta': '#930092', 25 | 'bright-cyan': '#0E6FAD', 26 | 'bright-white': '#FFFEFE', 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/cc-env-var-linked-services/cc-env-var-linked-services.types.d.ts: -------------------------------------------------------------------------------- 1 | import { EnvVar } from '../common.types.js'; 2 | 3 | export type EnvVarLinkedServicesState = 4 | | EnvVarLinkedServicesStateLoading 5 | | EnvVarLinkedServicesStateLoaded 6 | | EnvVarLinkedServicesStateError; 7 | 8 | interface EnvVarLinkedServicesStateLoading { 9 | type: 'loading'; 10 | } 11 | 12 | interface EnvVarLinkedServicesStateLoaded { 13 | type: 'loaded'; 14 | services: Array; 15 | } 16 | 17 | interface EnvVarLinkedServicesStateError { 18 | type: 'error'; 19 | } 20 | 21 | export type EnvVarLinkedServicesType = 'addon' | 'app'; 22 | 23 | export interface LinkedService { 24 | id: string; 25 | name: string; 26 | variables: Array; 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/ansi/palettes/tokyo-night-light.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { AnsiPalette } from '../ansi.types.js' 3 | */ 4 | 5 | /** @type {AnsiPalette} */ 6 | export default { 7 | foreground: '#565A6E', 8 | background: '#D5D6DB', 9 | 'background-hover': '#CBCCD1', 10 | 'background-selected': '#C1C2C7', 11 | black: '#0F0F14', 12 | red: '#8C4351', 13 | green: '#485E30', 14 | yellow: '#855215', 15 | blue: '#34548A', 16 | magenta: '#5A4A78', 17 | cyan: '#0F4B6E', 18 | white: '#343B58', 19 | 'bright-black': '#9699A3', 20 | 'bright-red': '#8C4351', 21 | 'bright-green': '#485E30', 22 | 'bright-yellow': '#855215', 23 | 'bright-blue': '#34548A', 24 | 'bright-magenta': '#5A4A78', 25 | 'bright-cyan': '#0F4B6E', 26 | 'bright-white': '#343B58', 27 | }; 28 | -------------------------------------------------------------------------------- /web-test-runner/cem-analyzer-plugin.js: -------------------------------------------------------------------------------- 1 | import { cli } from '@custom-elements-manifest/analyzer/cli.js'; 2 | 3 | const path = '/dist/custom-elements.json'; 4 | 5 | // This plugin generates and serves the CEM on demand. 6 | export const cemAnalyzerPlugin = { 7 | name: 'cem-analyzer-plugin', 8 | // we disable transform cache to enforce generation for every call 9 | transform(context) { 10 | if (context.path === path) { 11 | return { 12 | transformCache: false, 13 | }; 14 | } 15 | }, 16 | async serve(context) { 17 | if (context.path === path) { 18 | const cemJson = await cli({ 19 | argv: ['analyze'], 20 | noWrite: true, 21 | }); 22 | 23 | return JSON.stringify(cemJson); 24 | } 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/cc-addon-jenkins-options/cc-addon-jenkins-options.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-addon-jenkins-options.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Addon/', 7 | component: 'cc-addon-jenkins-options', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-addon-jenkins-options', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ options: [{ name: 'encryption', enabled: false }] }], 16 | }); 17 | 18 | export const encryptionEnabled = makeStory(conf, { 19 | items: [{ options: [{ name: 'encryption', enabled: true }] }], 20 | }); 21 | 22 | // This component isn't used when there are no options => no story for this case. 23 | -------------------------------------------------------------------------------- /src/components/cc-addon-mongodb-options/cc-addon-mongodb-options.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-addon-mongodb-options.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Addon/', 7 | component: 'cc-addon-mongodb-options', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-addon-mongodb-options', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ options: [{ name: 'encryption', enabled: false }] }], 16 | }); 17 | 18 | export const encryptionEnabled = makeStory(conf, { 19 | items: [{ options: [{ name: 'encryption', enabled: true }] }], 20 | }); 21 | 22 | // This component isn't used when there are no options => no story for this case. 23 | -------------------------------------------------------------------------------- /src/components/cc-feature-list/cc-feature-list.types.d.ts: -------------------------------------------------------------------------------- 1 | export type FeatureListState = FeatureListStateLoaded | FeatureListStateLoading | FeatureListStateError; 2 | 3 | export type FeatureStatus = 'alpha' | 'beta' | 'preview'; 4 | 5 | export interface Feature { 6 | id: string; 7 | name: string; 8 | description: string; 9 | options: { label: string; value: string }[]; 10 | value: string; 11 | status?: FeatureStatus; 12 | documentationLink?: string; 13 | feedbackLink?: string; 14 | } 15 | 16 | export interface FeatureListStateLoaded { 17 | type: 'loaded'; 18 | featureList: Feature[]; 19 | } 20 | 21 | export interface FeatureListStateLoading { 22 | type: 'loading'; 23 | } 24 | 25 | export interface FeatureListStateError { 26 | type: 'error'; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/cc-elasticsearch-info/cc-elasticsearch-info.types.d.ts: -------------------------------------------------------------------------------- 1 | export type ElasticSearchInfoState = 2 | | ElasticSearchInfoStateLoaded 3 | | ElasticSearchInfoStateLoading 4 | | ElasticSearchInfoStateError; 5 | 6 | export interface ElasticSearchInfoStateLoaded { 7 | type: 'loaded'; 8 | links: LinkLoaded[]; 9 | } 10 | 11 | export interface ElasticSearchInfoStateLoading { 12 | type: 'loading'; 13 | links: LinkLoading[]; 14 | } 15 | 16 | export interface ElasticSearchInfoStateError { 17 | type: 'error'; 18 | } 19 | 20 | export interface LinkLoaded { 21 | type: LinkType; 22 | href: string; 23 | } 24 | 25 | export interface LinkLoading { 26 | type: LinkType; 27 | href?: null; 28 | } 29 | 30 | export type LinkType = 'elasticsearch' | 'kibana' | 'apm'; 31 | -------------------------------------------------------------------------------- /src/components/cc-grafana-info/cc-grafana-info.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatch when grafana activation or deactivation is requested. 5 | * @extends {CcEvent<{isEnabled: boolean}>} 6 | */ 7 | export class CcGrafanaToggleEvent extends CcEvent { 8 | static TYPE = 'cc-grafana-toggle'; 9 | 10 | /** 11 | * @param {{isEnabled: boolean}} detail 12 | */ 13 | constructor(detail) { 14 | super(CcGrafanaToggleEvent.TYPE, detail); 15 | } 16 | } 17 | 18 | /** 19 | * Dispatch when grafana reset is requested. 20 | * @extends {CcEvent} 21 | */ 22 | export class CcGrafanaResetEvent extends CcEvent { 23 | static TYPE = 'cc-grafana-reset'; 24 | 25 | constructor() { 26 | super(CcGrafanaResetEvent.TYPE); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/cc-grafana-info/cc-grafana-info.types.d.ts: -------------------------------------------------------------------------------- 1 | export type GrafanaInfoState = GrafanaInfoStateLoading | GrafanaInfoStateLoadingError | GrafanaInfoStateLoaded; 2 | 3 | export interface GrafanaInfoStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | export interface GrafanaInfoStateLoadingError { 8 | type: 'error'; 9 | } 10 | 11 | export interface GrafanaInfoStateLoaded { 12 | type: 'loaded'; 13 | info: GrafanaInfo; 14 | } 15 | 16 | export type GrafanaInfo = GrafanaInfoEnabled | GrafanaInfoDisabled; 17 | 18 | export interface GrafanaInfoEnabled { 19 | status: 'enabled'; 20 | link?: string; 21 | action?: 'disabling' | 'resetting' | null; 22 | } 23 | 24 | export interface GrafanaInfoDisabled { 25 | status: 'disabled'; 26 | action?: 'enabling' | null; 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/baseline-newly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/cc-addon-postgresql-options/cc-addon-postgresql-options.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-addon-postgresql-options.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Addon/', 7 | component: 'cc-addon-postgresql-options', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-addon-postgresql-options', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ options: [{ name: 'encryption', enabled: false }] }], 16 | }); 17 | 18 | export const encryptionEnabled = makeStory(conf, { 19 | items: [{ options: [{ name: 'encryption', enabled: true }] }], 20 | }); 21 | 22 | // This component isn't used when there are no options => no story for this case. 23 | -------------------------------------------------------------------------------- /src/components/cc-tcp-redirection-form/cc-tcp-redirection-form.types.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TcpRedirectionStateLoaded, 3 | TcpRedirectionStateWaiting, 4 | } from '../cc-tcp-redirection/cc-tcp-redirection.types.js'; 5 | 6 | export type TcpRedirectionFormContextType = 'user' | 'admin'; 7 | 8 | export type TcpRedirectionFormState = 9 | | TcpRedirectionFormStateLoading 10 | | TcpRedirectionFormStateLoaded 11 | | TcpRedirectionFormStateError; 12 | 13 | export interface TcpRedirectionFormStateLoading { 14 | type: 'loading'; 15 | } 16 | 17 | export interface TcpRedirectionFormStateLoaded { 18 | type: 'loaded'; 19 | redirections: Array; 20 | } 21 | 22 | export interface TcpRedirectionFormStateError { 23 | type: 'error'; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/cc-logs-app-runtime/cc-logs-app-runtime.types.d.ts: -------------------------------------------------------------------------------- 1 | import { LogsStreamState } from '../../lib/logs/logs-stream.types.js'; 2 | import { GhostInstance, Instance } from '../cc-logs-instances/cc-logs-instances.types.js'; 3 | 4 | export interface LogsAppRuntimeStateLoadingInstances { 5 | type: 'loadingInstances'; 6 | } 7 | 8 | export interface LogsAppRuntimeStateErrorInstances { 9 | type: 'errorInstances'; 10 | } 11 | 12 | export interface LogsAppRuntimeStateLoaded { 13 | type: 'loaded'; 14 | streamState: LogsStreamState; 15 | instances: Array; 16 | selection: Array; 17 | } 18 | 19 | export type LogsAppRuntimeState = 20 | | LogsAppRuntimeStateLoadingInstances 21 | | LogsAppRuntimeStateErrorInstances 22 | | LogsAppRuntimeStateLoaded; 23 | -------------------------------------------------------------------------------- /src/components/cc-tcp-redirection/cc-tcp-redirection.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface TcpRedirection { 2 | namespace: string; 3 | isPrivate: boolean; 4 | sourcePort?: number | null; 5 | } 6 | 7 | export type TcpRedirectionState = TcpRedirectionStateLoading | TcpRedirectionStateLoaded | TcpRedirectionStateWaiting; 8 | 9 | export interface TcpRedirectionStateLoading { 10 | type: 'loading'; 11 | } 12 | 13 | export interface TcpRedirectionStateLoaded extends TcpRedirection { 14 | type: 'loaded'; 15 | } 16 | 17 | export interface TcpRedirectionStateWaiting extends TcpRedirection { 18 | type: 'waiting'; 19 | } 20 | 21 | export interface CreateTcpRedirection { 22 | namespace: string; 23 | } 24 | 25 | export interface DeleteTcpRedirection { 26 | namespace: string; 27 | sourcePort: number; 28 | } 29 | -------------------------------------------------------------------------------- /src/stories/assets/left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/stories/assets/right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/stories/fixtures/fake-map-data.js: -------------------------------------------------------------------------------- 1 | import fakePointsDataBig from './country-city-points-big-orga.js'; 2 | import fakePointsDataMedium from './country-city-points-medium-orga.js'; 3 | import fakePointsDataNormal from './country-city-points-normal-orga.js'; 4 | 5 | const FAKE_POINTS_DATA = [ 6 | fakePointsDataNormal, 7 | fakePointsDataMedium, 8 | fakePointsDataBig, 9 | ]; 10 | 11 | let lastSampleIndex; 12 | let fakeDataIndex = 0; 13 | 14 | export function getFakePointsData (sampleIndex) { 15 | if (sampleIndex !== lastSampleIndex) { 16 | fakeDataIndex = 0; 17 | lastSampleIndex = sampleIndex; 18 | } 19 | const data = Promise.resolve(FAKE_POINTS_DATA[sampleIndex][fakeDataIndex]); 20 | fakeDataIndex = (fakeDataIndex + 1) % FAKE_POINTS_DATA[sampleIndex].length; 21 | return data; 22 | } 23 | -------------------------------------------------------------------------------- /tasks/cdn-entry-name.js: -------------------------------------------------------------------------------- 1 | import semver from 'semver'; 2 | 3 | /** 4 | * 5 | * @param {CdnEnvironment} cdnEnvironment 6 | * @param {string} name 7 | * @return {string} 8 | */ 9 | export function toCdnEntryName(cdnEnvironment, name) { 10 | if (name == null) { 11 | throw new Error('Invalid CDN entry name: It cannot be null.'); 12 | } 13 | 14 | const result = name.trim(); 15 | 16 | if (result.length === 0) { 17 | throw new Error('Invalid CDN entry name: It cannot be empty.'); 18 | } 19 | 20 | if (cdnEnvironment.semver) { 21 | if (semver.valid(result) == null) { 22 | throw new Error(`Invalid CDN entry name '${result}': It must be a valid version.`); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | return result.replaceAll('/', '-').replaceAll(' ', '-'); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/cc-header-addon/cc-header-addon.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Addon, Zone } from '../common.types.js'; 2 | 3 | export type HeaderAddonState = 4 | | HeaderAddonStateLoaded 5 | | HeaderAddonStateLoadedWithVersion 6 | | HeaderAddonStateLoading 7 | | HeaderAddonStateError; 8 | 9 | export interface HeaderAddonStateLoaded extends Addon { 10 | type: 'loaded'; 11 | hasVersion: false; 12 | zone: Zone; 13 | } 14 | 15 | export interface HeaderAddonStateLoadedWithVersion extends Addon { 16 | type: 'loaded'; 17 | hasVersion: true; 18 | version: string; 19 | zone: Zone; 20 | } 21 | 22 | export interface HeaderAddonStateLoading { 23 | type: 'loading'; 24 | hasVersion: boolean; 25 | } 26 | 27 | export interface HeaderAddonStateError { 28 | type: 'error'; 29 | hasVersion: boolean; 30 | } 31 | -------------------------------------------------------------------------------- /src/stories/assets/justify.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/stories/assets/center.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/cc-token-api-update-form/cc-token-api-update-form.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TokenApiUpdateFormState = 2 | | TokenApiUpdateFormStateLoading 3 | | TokenApiUpdateFormStateError 4 | | TokenApiUpdateFormStateLoaded 5 | | TokenApiUpdateFormStateUpdating; 6 | 7 | export interface TokenApiUpdateFormStateLoading { 8 | type: 'loading'; 9 | } 10 | 11 | export interface TokenApiUpdateFormStateError { 12 | type: 'error'; 13 | } 14 | 15 | type FormValues = { 16 | name: string; 17 | description?: string; 18 | }; 19 | 20 | export interface TokenApiUpdateFormStateLoaded { 21 | type: 'loaded'; 22 | values: FormValues; 23 | } 24 | 25 | export interface TokenApiUpdateFormStateUpdating { 26 | type: 'updating'; 27 | values: FormValues; 28 | } 29 | 30 | export type CcTokenChangePayload = Required; 31 | -------------------------------------------------------------------------------- /test-mocha/cem/fixtures/cc-test-component-with-import.js: -------------------------------------------------------------------------------- 1 | import { LitElement } from 'lit'; 2 | 3 | /** 4 | * @import {Foo, Bar} from './cc-test-component.types.js' 5 | * @import {TheInterface} from './cc-test-component.types.js' 6 | * @import {TheType} from './cc-test-component.types.js' 7 | */ 8 | 9 | /** 10 | * Test component using @import syntax instead of @typedef 11 | */ 12 | // eslint-disable-next-line wc/define-tag-after-class-definition 13 | export class CcTestComponentWithImport extends LitElement { 14 | constructor() { 15 | super(); 16 | 17 | /** @type {Foo|Bar} - lorem ipsum. */ 18 | this.union = null; 19 | 20 | /** @type {TheInterface} - lorem ipsum. */ 21 | this.interface = null; 22 | 23 | /** @type {TheType} - lorem ipsum. */ 24 | this.typeDeclaration = null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/contributing/tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '👋 Contributing' 3 | title: 'Tools' 4 | --- 5 | 6 | # Tools reference 7 | 8 | Here's list of tools we recommand to work on this project: 9 | 10 | * [OctoLinker](https://github.com/OctoLinker/OctoLinker) 11 | * [Refined GitHub](https://github.com/refined-github/refined-github) 12 | * [VSCode](https://code.visualstudio.com/) 13 | * [lit-plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin) 14 | * [Inline HTML](https://marketplace.visualstudio.com/items?itemName=pushqrdx.inline-html) 15 | * [WebStorm](https://www.jetbrains.com/fr-fr/webstorm/) 16 | * [clever-tools](https://github.com/CleverCloud/clever-tools) 17 | * [s3cmd](https://s3tools.org/s3cmd) 18 | * [Volta](https://volta.sh/) 19 | * [Web Component Dev Tools](https://open-wc.org/guides/tools/web-component-devtools/#download) 20 | -------------------------------------------------------------------------------- /sandbox/sandbox-styles.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | // language=CSS 4 | export const sandboxStyles = css` 5 | :host { 6 | display: grid; 7 | gap: 1em; 8 | grid-template-areas: 9 | 'ctrl-top .' 10 | 'main ctrl-right'; 11 | grid-template-columns: 1fr max-content; 12 | grid-template-rows: max-content 1fr; 13 | } 14 | 15 | .ctrl-top { 16 | align-items: center; 17 | display: flex; 18 | flex-wrap: wrap; 19 | gap: 0.5em; 20 | grid-area: ctrl-top; 21 | } 22 | 23 | .ctrl-right { 24 | display: flex; 25 | flex-direction: column; 26 | gap: 0.5em; 27 | grid-area: ctrl-right; 28 | } 29 | 30 | .main { 31 | background-color: #f1f4f9; /* this is the color used in console3 */ 32 | border: 1px solid #8aa3cd; 33 | grid-area: main; 34 | padding: 0.5em; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/components/cc-logs-control/cc-logs-control.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Timezone } from '../../lib/date/date.types.js'; 2 | import { DateDisplay } from '../cc-logs/date-display.types.js'; 3 | 4 | export type LogsControlPalette = 'default' | 'One Light' | 'Tokyo Night Light' | 'Night Owl' | 'Everblush' | 'Hyoob'; 5 | 6 | export interface LogsMetadataDisplay { 7 | label: string; 8 | hidden: boolean; 9 | } 10 | 11 | export interface LogsOptions { 12 | 'date-display': DateDisplay; 13 | 'metadata-display': Record; 14 | palette: LogsControlPalette; 15 | timezone: Timezone; 16 | 'wrap-lines': boolean; 17 | 'strip-ansi': boolean; 18 | } 19 | 20 | export interface LogsOptionsChangeEventData { 21 | /** The name of the option that has changed */ 22 | name: keyof LogsOptions; 23 | /** The new options */ 24 | options: LogsOptions; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/cc-pricing-product-consumption/cc-pricing-product-consumption.types.d.ts: -------------------------------------------------------------------------------- 1 | import { PricingSection, SectionType } from '../common.types.js'; 2 | 3 | interface PricingProductConsumptionStateLoading { 4 | type: 'loading'; 5 | } 6 | 7 | interface PricingProductConsumptionStateError { 8 | type: 'error'; 9 | } 10 | 11 | interface PricingProductConsumptionStateLoaded { 12 | type: 'loaded'; 13 | name: string; 14 | sections: PricingSection[]; 15 | } 16 | 17 | export type PricingProductConsumptionState = 18 | | PricingProductConsumptionStateLoading 19 | | PricingProductConsumptionStateError 20 | | PricingProductConsumptionStateLoaded; 21 | 22 | export type SectionStates = Partial< 23 | Record< 24 | SectionType, 25 | { 26 | isClosed: boolean; 27 | quantity: number; 28 | unitValue: string; 29 | } 30 | > 31 | >; 32 | -------------------------------------------------------------------------------- /src/lib/notifications.js: -------------------------------------------------------------------------------- 1 | import { CcNotifyEvent } from './notifications.events.js'; 2 | 3 | /** 4 | * @import { Notification } from '../components/common.types.js' 5 | */ 6 | 7 | /** 8 | * @param {Notification} notification 9 | * @param {Window|Node} target 10 | */ 11 | export function notify(notification, target = window) { 12 | target.dispatchEvent(new CcNotifyEvent(notification)); 13 | } 14 | 15 | /** 16 | * @param {string|Node} message 17 | * @param {string} [title] 18 | */ 19 | export function notifyError(message, title) { 20 | notify({ 21 | message, 22 | title, 23 | intent: 'danger', 24 | }); 25 | } 26 | 27 | /** 28 | * @param {string|Node} message 29 | * @param {string} [title] 30 | */ 31 | export function notifySuccess(message, title) { 32 | notify({ 33 | message, 34 | title, 35 | intent: 'success', 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /docs/getting-started/manual-installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '🏡 Getting Started' 3 | title: 'Manual installation' 4 | --- 5 | 6 | # How to install components manually? 7 | 8 | ## Theme 9 | 10 | 11 | With this solution, you won't get automatic updates. You will need to check the changelog for potential breaking changes and update your local copy every time. 12 | 13 | 14 | 15 | 16 | If your project toolchain is not based on Node.js and npm, you can copy-paste the CSS file in your own source code. 17 | 18 | The CSS file to copy is available at (replace `` with the one you're using): 19 | 20 | ``` 21 | https://github.com/CleverCloud/clever-components/blob//src/styles/default-theme.css 22 | ``` 23 | -------------------------------------------------------------------------------- /src/components/cc-map-marker-server/cc-map-marker-server.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-map-marker-server.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Maps/', 7 | component: 'cc-map-marker-server', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-map-marker-server', 12 | displayMode: 'flex-wrap', 13 | }; 14 | 15 | export const defaultStory = makeStory(conf, { 16 | items: [{ state: 'default' }, { state: 'hovered' }, { state: 'selected' }], 17 | }); 18 | 19 | export const stateWithDefault = makeStory(conf, { 20 | items: [{ state: 'default' }], 21 | }); 22 | 23 | export const stateWithHovered = makeStory(conf, { 24 | items: [{ state: 'hovered' }], 25 | }); 26 | 27 | export const stateWithSelected = makeStory(conf, { 28 | items: [{ state: 'selected' }], 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/cc-logs/cc-logs.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { Log } from './cc-logs.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the logs follow state changed after a user interaction. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcLogsFollowChangeEvent extends CcEvent { 12 | static TYPE = 'cc-logs-follow-change'; 13 | 14 | /** 15 | * @param {boolean} detail 16 | */ 17 | constructor(detail) { 18 | super(CcLogsFollowChangeEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when log inspect is requested 24 | * @extends {CcEvent} 25 | */ 26 | export class CcLogInspectEvent extends CcEvent { 27 | static TYPE = 'cc-log-inspect'; 28 | 29 | /** 30 | * @param {Log} detail 31 | */ 32 | constructor(detail) { 33 | super(CcLogInspectEvent.TYPE, detail); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/i18n/i18n-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prepares a plural() function for a given lang 3 | * 4 | * @param {string} lang - BCP 47 language tag 5 | * @returns {(number: number, one: string, other?: string) => string} 6 | */ 7 | export function preparePlural(lang) { 8 | const pr = new Intl.PluralRules(lang); 9 | 10 | /** 11 | * Based on https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules 12 | * 13 | * @param {number} number - The number to get a plural rule for. 14 | * @param {string} one - The singular form of the string. 15 | * @param {string} [other] - The plural form of the string (optional with automatic "s" suffix). 16 | * @returns {string} 17 | */ 18 | return function plural(number, one, other = one + 's') { 19 | const rules = { zero: one, one, two: other, few: other, many: other, other }; 20 | return rules[pr.select(number)]; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/skeleton.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | // language=CSS 4 | export const skeletonStyles = css` 5 | @keyframes skeleton-pulse { 6 | from { 7 | opacity: 0.85; 8 | } 9 | 10 | to { 11 | opacity: 0.45; 12 | } 13 | } 14 | 15 | /* FIXME: temporary solution for Safari until we build a directive for skeletons. See https://github.com/CleverCloud/clever-components/issues/1111 for more info */ 16 | * { 17 | animation: none; 18 | } 19 | 20 | .skeleton { 21 | animation-direction: alternate; 22 | animation-duration: 500ms; 23 | animation-iteration-count: infinite; 24 | animation-name: skeleton-pulse; 25 | animation-play-state: var(--cc-skeleton-state, running); 26 | color: transparent; 27 | cursor: progress; 28 | -moz-user-select: none; 29 | -webkit-user-select: none; 30 | -ms-user-select: none; 31 | user-select: none; 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /src/components/cc-code/cc-code.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-code.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🧬 Atoms/', 7 | component: 'cc-code', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-code', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{ innerHTML: `clever help` }], 16 | }); 17 | 18 | export const multiLines = makeStory(conf, { 19 | items: [ 20 | { 21 | innerHTML: ` 22 | { 23 | "name" : "myApp", 24 | "version" : "0.1.0", 25 | "main" : "myApp.js", 26 | } 27 | `, 28 | }, 29 | ], 30 | }); 31 | 32 | export const longLine = makeStory(conf, { 33 | items: [ 34 | { 35 | innerHTML: `GET /api/orders/checkout?session_id=cs_test_a1B2c3D4E5f6G7h8I9J0kLmNoPqRsTuVwXyZ1234567890abcdef1234567890abcdef HTTP/1.1`, 36 | }, 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/cc-env-var-create/cc-env-var-create.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-env-var-create.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🛠 Environment variables/', 7 | component: 'cc-env-var-create', 8 | }; 9 | 10 | const conf = { 11 | component: 'cc-env-var-create', 12 | }; 13 | 14 | export const defaultStory = makeStory(conf, { 15 | items: [{}], 16 | }); 17 | 18 | export const validationWithExistingNames = makeStory(conf, { 19 | docs: 'In this example `FOO` and `BAR` are already defined and cannot be used as a variable name again.', 20 | items: [{ variablesNames: ['FOO', 'BAR'], _variableName: 'FOO' }], 21 | }); 22 | 23 | export const validationWithStrictMode = makeStory(conf, { 24 | items: [{ validationMode: 'strict' }], 25 | }); 26 | 27 | export const disabled = makeStory(conf, { 28 | items: [{ disabled: true }], 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/cc-token-api-update-form/cc-token-api-update-form.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { CcTokenChangePayload } from './cc-token-api-update-form.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the update token form is submitted 9 | * @extends {CcEvent} 10 | */ 11 | export class CcTokenChangeEvent extends CcEvent { 12 | static TYPE = 'cc-token-change'; 13 | 14 | /** @param {CcTokenChangePayload} detail */ 15 | constructor(detail) { 16 | super(CcTokenChangeEvent.TYPE, detail); 17 | } 18 | } 19 | 20 | /** 21 | * Dispatched when the API token has been updated successfully 22 | * @extends {CcEvent} 23 | */ 24 | export class CcTokenWasUpdatedEvent extends CcEvent { 25 | static TYPE = 'cc-token-was-updated'; 26 | 27 | /** @param {string} detail */ 28 | constructor(detail) { 29 | super(CcTokenWasUpdatedEvent.TYPE, detail); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/cc-kv-terminal/cc-kv-terminal.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { CcKvTerminalState } from './cc-kv-terminal.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when KV terminal state changes. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcKvTerminalStateChangeEvent extends CcEvent { 12 | static TYPE = 'cc-kv-terminal-state-change'; 13 | 14 | /** 15 | * @param {CcKvTerminalState} detail 16 | */ 17 | constructor(detail) { 18 | super(CcKvTerminalStateChangeEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when a KV command execution is requested. 24 | * @extends {CcEvent} 25 | */ 26 | export class CcKvCommandExecuteEvent extends CcEvent { 27 | static TYPE = 'cc-kv-command-execute'; 28 | 29 | /** 30 | * @param {string} detail 31 | */ 32 | constructor(detail) { 33 | super(CcKvCommandExecuteEvent.TYPE, detail); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web-test-runner/wds-common.js: -------------------------------------------------------------------------------- 1 | import rollupCommonjs from '@rollup/plugin-commonjs'; 2 | import { fromRollup } from '@web/dev-server-rollup'; 3 | import { esbuildBundlePlugin } from './esbuild-bundle-plugin.js'; 4 | 5 | const commonjs = fromRollup(rollupCommonjs); 6 | 7 | function commonJsIdentifiers(ids) { 8 | return ids.map((id) => `**/node_modules/${id}/**/*`); 9 | } 10 | 11 | // regroup ES module files into one bundle 12 | export const esbuildBundlePluginWithConfig = esbuildBundlePlugin({ 13 | pathsToBundle: ['/src/lib/leaflet-esm.js', '/node_modules/chart.js/dist/chart.esm.js'], 14 | }); 15 | 16 | // convert CommonJS modules to ES6 to be included in the rollup bundle 17 | export const commonjsPluginWithConfig = commonjs({ 18 | // the commonjs plugin is slow, list the required packages explicitly: 19 | include: commonJsIdentifiers([ 20 | 'statuses', 21 | // used by clever-client 22 | 'oauth-1.0a', 23 | 'component-emitter', 24 | ]), 25 | }); 26 | -------------------------------------------------------------------------------- /src/templates/cc-addon-encryption-at-rest-option/cc-addon-encryption-at-rest-option.js: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { iconRemixShieldKeyholeFill as iconEncryptionAtRest } from '../../assets/cc-remix.icons.js'; 3 | import '../../components/cc-icon/cc-icon.js'; 4 | import { i18n } from '../../translations/translation.js'; 5 | 6 | /** 7 | * @import { EncryptionAddonOption, AddonOptionWithMetadata } from '../../components/common.types.js' 8 | */ 9 | 10 | /** @type {(option: EncryptionAddonOption) => AddonOptionWithMetadata & EncryptionAddonOption} */ 11 | export const ccAddonEncryptionAtRestOption = ({ enabled }) => { 12 | const description = html` 13 | ${i18n('cc-addon-encryption-at-rest-option.description')} 14 | `; 15 | 16 | return { 17 | title: i18n('cc-addon-encryption-at-rest-option.title'), 18 | icon: iconEncryptionAtRest, 19 | description, 20 | enabled, 21 | name: 'encryption', 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/cc-logs-date-range-selector/cc-logs-date-range-selector.types.d.ts: -------------------------------------------------------------------------------- 1 | import { DateRange } from '../../lib/date/date-range.types.js'; 2 | 3 | export type LogsDateRangeSelection = 4 | | LogsDateRangeSelectionLive 5 | | LogsDateRangeSelectionPreset 6 | | LogsDateRangeSelectionCustom; 7 | 8 | export interface LogsDateRangeSelectionLive { 9 | type: 'live'; 10 | } 11 | 12 | export interface LogsDateRangeSelectionPreset { 13 | type: 'preset'; 14 | preset: LogsDateRangePresetType; 15 | } 16 | 17 | export interface LogsDateRangeSelectionCustom { 18 | type: 'custom'; 19 | since: string; 20 | until: string; 21 | } 22 | 23 | export type LogsDateRangePresetType = 'lastHour' | 'last4Hours' | 'last7Days' | 'today' | 'yesterday'; 24 | 25 | export type LogsDateRangeSelectOption = 'live' | 'custom' | LogsDateRangePresetType; 26 | 27 | export interface LogsDateRangeSelectionChangeEventData { 28 | selection: LogsDateRangeSelection; 29 | range: DateRange; 30 | } 31 | -------------------------------------------------------------------------------- /test/helpers/story-testing-utils.types.d.ts: -------------------------------------------------------------------------------- 1 | import type { AnnotatedStoryFn } from '@storybook/csf'; 2 | import type { StoryObj, WebComponentsRenderer } from '@storybook/web-components'; 3 | 4 | export type StoryParams = StoryObj['parameters']; 5 | 6 | export type AccessibilityTestOptions = { 7 | tests: { 8 | accessibility: { 9 | enable: boolean; 10 | ignoredRules: Array; 11 | }; 12 | }; 13 | }; 14 | 15 | export type AnnotatedStoryFnClever = AnnotatedStoryFn & { 16 | parameters: StoryParams & AccessibilityTestOptions; 17 | }; 18 | 19 | export type RawStoriesModule = { 20 | default: { 21 | component: keyof HTMLElementTagNameMap; 22 | }; 23 | } & { [storyName: string]: AnnotatedStoryFnClever } & {}; 24 | 25 | export type ArrayOfStoryFunctions = Array<{ storyName: string; storyFunction: AnnotatedStoryFnClever }>; 26 | 27 | export type ModuleEntryWithStoryFunction = [storyName: string, storyFunction: AnnotatedStoryFnClever]; 28 | -------------------------------------------------------------------------------- /src/components/cc-orga-member-list/cc-orga-member-list.types.d.ts: -------------------------------------------------------------------------------- 1 | import { OrgaMemberCardState, OrgaMemberRole } from '../cc-orga-member-card/cc-orga-member-card.types.js'; 2 | 3 | export type OrgaMemberListState = OrgaMemberListStateLoading | OrgaMemberListStateLoaded | OrgaMemberListStateError; 4 | 5 | interface OrgaMemberListStateLoading { 6 | type: 'loading'; 7 | } 8 | 9 | interface OrgaMemberListStateLoaded { 10 | type: 'loaded'; 11 | memberList: OrgaMemberCardState[]; 12 | identityFilter: string; 13 | mfaDisabledOnlyFilter: boolean; 14 | dangerZoneState: 'idle' | 'leaving' | 'error'; 15 | } 16 | 17 | interface OrgaMemberListStateError { 18 | type: 'error'; 19 | } 20 | 21 | export interface InviteMember { 22 | email: string; 23 | role: OrgaMemberRole; 24 | } 25 | 26 | interface ListAuthorisations { 27 | invite: boolean; 28 | edit: boolean; 29 | delete: boolean; 30 | } 31 | 32 | export interface InviteMemberFormState { 33 | type: 'idle' | 'inviting'; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/cc-kv-terminal/cc-kv-terminal.types.d.ts: -------------------------------------------------------------------------------- 1 | export type CcKvTerminalState = CcKvTerminalStateIdle | CcKvTerminalStateRunning; 2 | 3 | export interface CcKvTerminalStateIdle { 4 | type: 'idle'; 5 | history: Array; 6 | } 7 | 8 | export interface CcKvTerminalStateRunning { 9 | type: 'running'; 10 | commandLine: string; 11 | history: Array; 12 | } 13 | 14 | export interface CcKvCommandHistoryEntry { 15 | commandLine: string; 16 | result: Array; 17 | success: boolean; 18 | } 19 | 20 | export type CcKvCommandContentItem = CcKvCommandContentItemCommandLine | CcKvCommandContentItemResultLine; 21 | 22 | interface CcKvCommandContentItemCommandLine { 23 | id: string; 24 | type: 'commandLine'; 25 | line: string; 26 | hasResult: boolean; 27 | } 28 | 29 | interface CcKvCommandContentItemResultLine { 30 | id: string; 31 | type: 'resultLine'; 32 | line: string; 33 | success: boolean; 34 | last: boolean; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/cc-pricing-estimation/cc-pricing-estimation.types.d.ts: -------------------------------------------------------------------------------- 1 | import { ConsumptionPlan, Plan, PricingSection } from '../common.types.js'; 2 | 3 | export type PricingEstimationState = 4 | | PricingEstimationStateLoaded 5 | | PricingEstimationStateLoading 6 | | PricingEstimationStateError; 7 | 8 | export interface PricingEstimationStateError { 9 | type: 'error'; 10 | } 11 | 12 | export interface PricingEstimationStateLoading { 13 | type: 'loading'; 14 | } 15 | 16 | export interface PricingEstimationStateLoaded { 17 | type: 'loaded'; 18 | runtimePrices: Array; 19 | countablePrices: Array; 20 | } 21 | 22 | export interface FormattedRuntimePrice { 23 | priceId: string; 24 | price: number; 25 | } 26 | 27 | export interface RuntimePlanWithQuantity extends Plan { 28 | quantity: number; 29 | } 30 | 31 | export interface CountablePlanWithQuantity extends ConsumptionPlan { 32 | quantity: number; 33 | sections: PricingSection[]; 34 | } 35 | -------------------------------------------------------------------------------- /web-test-runner/esbuild-bundle-plugin.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild'; 2 | 3 | // /!\ This is an experimental plugin for now 4 | // We created this because some of our deps have way to many separated ES module files (Leaflet, chart.js...) 5 | export function esbuildBundlePlugin({ pathsToBundle }) { 6 | return { 7 | async transform(context) { 8 | if (pathsToBundle.includes(context.request.url)) { 9 | const bundle = esbuild.buildSync({ 10 | entryPoints: [context.request.url.slice(1)], 11 | bundle: true, 12 | format: 'esm', 13 | minify: true, 14 | write: false, 15 | // prevents esbuild from bundling types instead of the actual package 16 | // because of the `paths` option within the regular `tsconfig.json` file 17 | tsconfig: 'src/lib/leaflet/tsconfig-empty.json', 18 | }); 19 | 20 | const code = bundle.outputFiles[0].text; 21 | 22 | return code; 23 | } 24 | }, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/cc-addon-backups/cc-addon-backups.types.d.ts: -------------------------------------------------------------------------------- 1 | export type AddonBackupsState = AddonBackupsStateLoaded | AddonBackupsStateLoading | AddonBackupsStateError; 2 | 3 | export interface AddonBackupsStateLoaded { 4 | type: 'loaded'; 5 | backups: Backup[]; 6 | providerId: ProviderId; 7 | passwordForCommand: string; 8 | } 9 | 10 | export interface AddonBackupsStateLoading { 11 | type: 'loading'; 12 | } 13 | 14 | export interface AddonBackupsStateError { 15 | type: 'error'; 16 | } 17 | 18 | export interface Backup { 19 | createdAt: string | number; // date as a string or timestamp 20 | expiresAt?: string | number; // date as a string or timestamp 21 | url: string; 22 | restoreCommand?: string; 23 | deleteCommand?: string; 24 | } 25 | 26 | export type OverlayType = 'restore' | 'delete'; 27 | 28 | export type ProviderId = 29 | | 'es-addon' 30 | | 'es-addon-old' 31 | | 'postgresql-addon' 32 | | 'mysql-addon' 33 | | 'mongodb-addon' 34 | | 'redis-addon' 35 | | 'jenkins'; 36 | -------------------------------------------------------------------------------- /tsconfig.ci.json: -------------------------------------------------------------------------------- 1 | { 2 | // this file is used for typechecking in the CI context 3 | // as opposed to the `tsconfig.json` file used for typechecking within IDEs 4 | // Every file passing typechecking should be added in the `include` array below 5 | "include": [ 6 | ".storybook/**/*.js", 7 | "src/lib/**/*.js", 8 | "src/components/**/*.js", 9 | "src/controllers/*.js", 10 | "src/templates/*.js", 11 | "src/translations/**/*.js", 12 | "tasks/typechecking-stats.js", 13 | "src/lib/events-map.types.d.ts" 14 | ], 15 | "exclude": ["src/controllers/*.stories.js", "src/components/**/*.test.js", "src/components/**/*.stories.js"], 16 | "extends": "./tsconfig.json", 17 | "compilerOptions": { 18 | // FIXME: 19 | // This means our `d.ts` files are not checked in CI context 20 | // This needs to be set to `true` at least until we can upgrade TypeScript 21 | // see https://github.com/CleverCloud/clever-components/issues/972 22 | "skipLibCheck": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/cc-env-var-form/cc-env-var-form.types.d.ts: -------------------------------------------------------------------------------- 1 | import { EnvVar, EnvVarValidationMode } from '../common.types.js'; 2 | 3 | export type EnvVarFormState = 4 | | EnvVarFormStateLoading 5 | | EnvVarFormStateLoaded 6 | | EnvVarFormStateSaving 7 | | EnvVarFormStateError; 8 | 9 | export interface EnvVarFormStateLoading { 10 | type: 'loading'; 11 | } 12 | 13 | export interface EnvVarFormStateLoaded { 14 | type: 'loaded'; 15 | variables: Array; 16 | validationMode: EnvVarValidationMode; 17 | } 18 | 19 | export interface EnvVarFormStateSaving { 20 | type: 'saving'; 21 | variables: Array; 22 | validationMode: EnvVarValidationMode; 23 | } 24 | 25 | export interface EnvVarFormStateError { 26 | type: 'error'; 27 | } 28 | 29 | type EnvVarFormContextType = 30 | | 'env-var-app' 31 | | 'env-var-simple' 32 | | 'env-var-addon' 33 | | 'exposed-config' 34 | | 'config-provider' 35 | | 'linked-app' 36 | | 'linked-addon'; 37 | 38 | export type EnvVarFormMode = 'SIMPLE' | 'EXPERT' | 'JSON'; 39 | -------------------------------------------------------------------------------- /src/translations/translation.types.d.ts: -------------------------------------------------------------------------------- 1 | import { translations as enTranslations } from './translations.en.js'; 2 | import { translations as frTranslations } from './translations.fr.js'; 3 | 4 | type Translations = typeof frTranslations & typeof enTranslations; 5 | type TranslationKeys = keyof Translations; 6 | type ArrowFunction = (...args: any) => any; 7 | 8 | export type I18nFunction = ( 9 | // the rest of the arguments depends on the type of the TranslationPropertyValue which can be either a function or a string (see (i18n.types.d.ts).Translation) 10 | // if it's a string, we do not allow any arguments 11 | // if it's a function, we allow one argument with the same type as the one expected by the function 12 | ...args: TranslationPropertyValue extends ArrowFunction ? [Key, Parameters[0]] : [Key] 13 | ) => TranslationPropertyValue extends ArrowFunction ? ReturnType : TranslationPropertyValue; 14 | -------------------------------------------------------------------------------- /src/lib/form/form.types.d.ts: -------------------------------------------------------------------------------- 1 | import { EventWithTarget } from '../events.types.js'; 2 | import { Validity } from './validation.types.js'; 3 | 4 | export type FormControlData = null | File | string | FormData; 5 | 6 | export type FormDataMap = { [key: string]: FormControlData | Array }; 7 | 8 | export interface FormControlElementLike { 9 | get willValidate(): boolean; 10 | checkValidity: () => boolean; 11 | get validationMessage(): string; 12 | } 13 | 14 | export type OnValidCallback = (formData: FormDataMap, formElement: HTMLFormElement) => void; 15 | export type OnInvalidCallback = (formValidity: FormValidity, formElement: HTMLFormElement) => void; 16 | 17 | export interface SubmitHandlerCallbacks { 18 | onValid?: OnValidCallback; 19 | onInvalid?: OnInvalidCallback; 20 | } 21 | 22 | export type HTMLFormElementEvent = EventWithTarget; 23 | 24 | export type FormValidity = Array; 25 | 26 | interface FormControlValidity { 27 | name: string; 28 | validity: Validity; 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cc-logs-loading-progress/cc-logs-loading-progress.types.d.ts: -------------------------------------------------------------------------------- 1 | export type LogsLoadingProgressState = 2 | | LogsLoadingProgressStateIdle 3 | | LogsLoadingProgressStateRunning 4 | | LogsLoadingProgressStatePaused 5 | | LogsLoadingProgressStateOverflowLimitReached 6 | | LogsLoadingProgressStateCompleted; 7 | 8 | export interface LogsLoadingProgressStateIdle { 9 | type: 'idle'; 10 | } 11 | 12 | export interface LogsLoadingProgressStateRunning { 13 | type: 'running'; 14 | value: number; 15 | percent?: number; 16 | overflowing: boolean; 17 | } 18 | 19 | export interface LogsLoadingProgressStatePaused { 20 | type: 'paused'; 21 | value: number; 22 | percent?: number; 23 | overflowing: boolean; 24 | } 25 | 26 | export interface LogsLoadingProgressStateOverflowLimitReached { 27 | type: 'overflowLimitReached'; 28 | value: number; 29 | percent?: number; 30 | } 31 | 32 | export interface LogsLoadingProgressStateCompleted { 33 | type: 'completed'; 34 | value: number; 35 | overflowing: boolean; 36 | } 37 | -------------------------------------------------------------------------------- /test/dom/dom.test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web-test-runner/story-file-to-a11y-tests-file-plugin.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@web/test-runner').TestRunnerPlugin} */ 2 | export const storyFileToA11yTestsFilePlugin = { 3 | name: 'story-file-to-a11y-tests-file', 4 | async transformImport({ source, context }) { 5 | // if `.stories.js` is imported by WTR itself, then we change it to import the test file 6 | if (context.request.url.startsWith('/?wtr-session-id=') && source.includes('.stories.js?wtr-session')) { 7 | return source.replace('.stories.js', '.stories.test.js'); 8 | } 9 | }, 10 | async serve(context) { 11 | // test files are generated on the fly and they import story modules 12 | if (context.path.endsWith('.stories.test.js')) { 13 | const testFileContent = ` 14 | import { runA11yTests } from '/test/helpers/a11y-tests.js'; 15 | import * as storiesModule from '${context.path.replace('.stories.test.js', '.stories.js')}'; 16 | 17 | runA11yTests(storiesModule); 18 | `; 19 | return testFileContent; 20 | } 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/cc-zone-input/cc-zone-input.types.d.ts: -------------------------------------------------------------------------------- 1 | import { ZoneModeType, ZoneStateLoaded } from '../cc-zone/cc-zone.types.js'; 2 | import { Point, Zone } from '../common.types.js'; 3 | 4 | export type ZoneInputState = ZoneInputStateLoaded | ZoneInputStateLoading | ZoneInputStateError; 5 | 6 | export interface ZoneInputStateLoaded { 7 | type: 'loaded'; 8 | zones: Zone[]; 9 | } 10 | 11 | export interface ZoneInputStateLoading { 12 | type: 'loading'; 13 | } 14 | 15 | export interface ZoneInputStateError { 16 | type: 'error'; 17 | } 18 | 19 | // FIXME: the `Point` type has a different type for `marker` & `tooltip` properties 20 | // maybe we could add this as an alternative within the original `Point` interface? 21 | export interface ZoneInputPoint extends Omit { 22 | marker: { tag: 'cc-map-marker-server'; state: ZonePointMarkerState; keyboard: false }; 23 | tooltip: { tag: 'cc-zone'; state: ZoneStateLoaded; mode: ZoneModeType }; 24 | } 25 | 26 | export type ZonePointMarkerState = 'selected' | 'hovered' | 'default'; 27 | -------------------------------------------------------------------------------- /src/components/cc-addon-admin/cc-addon-admin.types.d.ts: -------------------------------------------------------------------------------- 1 | export type AddonAdminState = 2 | | AddonAdminStateLoaded 3 | | AddonAdminStateLoading 4 | | AddonAdminStateError 5 | | AddonAdminStateSaving; 6 | 7 | export interface AddonAdminStateLoaded extends AddonAdminStateBase { 8 | type: 'loaded'; 9 | } 10 | 11 | export interface AddonAdminStateLoading { 12 | type: 'loading'; 13 | } 14 | 15 | export interface AddonAdminStateError { 16 | type: 'error'; 17 | } 18 | 19 | export type AddonAdminStateSaving = AddonAdminStateDeleting | AddonAdminStateUpdatingName | AddonAdminStateUpdatingTags; 20 | 21 | export interface AddonAdminStateDeleting extends AddonAdminStateBase { 22 | type: 'deleting'; 23 | } 24 | 25 | export interface AddonAdminStateUpdatingName extends AddonAdminStateBase { 26 | type: 'updating-name'; 27 | } 28 | 29 | export interface AddonAdminStateUpdatingTags extends AddonAdminStateBase { 30 | type: 'updating-tags'; 31 | } 32 | 33 | interface AddonAdminStateBase { 34 | id: string; 35 | name: string; 36 | tags: string[]; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/cc-orga-member-list/cc-orga-member-list.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { InviteMember } from './cc-orga-member-list.types.js' 5 | * @import { OrgaMember } from '../cc-orga-member-card/cc-orga-member-card.types.js' 6 | */ 7 | 8 | /** 9 | * Dispatched when an organisation member invitation is requested. 10 | * @extends {CcEvent} 11 | */ 12 | export class CcOrgaMemberInviteEvent extends CcEvent { 13 | static TYPE = 'cc-orga-member-invite'; 14 | 15 | /** 16 | * @param {InviteMember} detail 17 | */ 18 | constructor(detail) { 19 | super(CcOrgaMemberInviteEvent.TYPE, detail); 20 | } 21 | } 22 | 23 | /** 24 | * Dispatched when a member has left the organisation. 25 | * @extends {CcEvent} 26 | */ 27 | export class CcOrgaMemberLeftEvent extends CcEvent { 28 | static TYPE = 'cc-orga-member-left'; 29 | 30 | /** 31 | * @param {OrgaMember} detail 32 | */ 33 | constructor(detail) { 34 | super(CcOrgaMemberLeftEvent.TYPE, detail); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /eslint/lit/eslint-config-lit-clever-cloud.js: -------------------------------------------------------------------------------- 1 | import litPlugin from 'eslint-plugin-lit'; 2 | 3 | export default { 4 | name: 'lit-cc', 5 | files: ['**/*.js'], 6 | plugins: { 7 | lit: litPlugin, 8 | }, 9 | rules: { 10 | ...litPlugin.configs['flat/recommended'].rules, 11 | 'lit/attribute-names': 'error', 12 | 'lit/lifecycle-super': 'error', 13 | 'lit/ban-attributes': ['error', 'checked'], 14 | 'lit/no-classfield-shadowing': 'error', 15 | 'lit/no-invalid-escape-sequences': 'error', 16 | 'lit/no-legacy-imports': 'error', 17 | 'lit/no-legacy-template-syntax': 'error', 18 | 'lit/no-native-attributes': 'error', 19 | 'lit/no-private-properties': ['error', { private: '^[_$]' }], 20 | 'lit/no-property-change-update': 'error', 21 | 'lit/no-template-bind': 'error', 22 | 'lit/no-this-assign-in-render': 'error', 23 | 'lit/no-useless-template-literals': 'error', 24 | 'lit/no-value-attribute': 'error', 25 | 'lit/prefer-static-styles': 'error', 26 | 'lit/value-after-constraints': 'error', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/cc-tcp-redirection/cc-tcp-redirection.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { CreateTcpRedirection, DeleteTcpRedirection } from './cc-tcp-redirection.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when a tcp redirection creation is requested. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcTcpRedirectionCreateEvent extends CcEvent { 12 | static TYPE = 'cc-tcp-redirection-create'; 13 | 14 | /** 15 | * @param {CreateTcpRedirection} detail 16 | */ 17 | constructor(detail) { 18 | super(CcTcpRedirectionCreateEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when a tcp redirection deletion is requested. 24 | * @extends {CcEvent} 25 | */ 26 | export class CcTcpRedirectionDeleteEvent extends CcEvent { 27 | static TYPE = 'cc-tcp-redirection-delete'; 28 | 29 | /** 30 | * @param {DeleteTcpRedirection} detail 31 | */ 32 | constructor(detail) { 33 | super(CcTcpRedirectionDeleteEvent.TYPE, detail); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web-test-runner/visual-tests/story-file-to-visual-tests-file-plugin.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@web/test-runner').TestRunnerPlugin} */ 2 | export const storyFileToVisualTestsFilePlugin = { 3 | name: 'story-file-to-visual-test-file', 4 | async transformImport({ source, context }) { 5 | // if `.stories.js` is imported by WTR itself, then we change it to import the test file 6 | if (context.request.url.startsWith('/?wtr-session-id=') && source.includes('.stories.js?wtr-session')) { 7 | return source.replace('.stories.js', '.stories.test.js'); 8 | } 9 | }, 10 | async serve(context) { 11 | // test files are generated on the fly and they import story modules 12 | if (context.path.endsWith('.stories.test.js')) { 13 | const testFileContent = ` 14 | import { runVisualTests } from '/test/helpers/visual-tests.js'; 15 | import * as storiesModule from '${context.path.replace('.stories.test.js', '.stories.js')}'; 16 | 17 | runVisualTests(storiesModule); 18 | `; 19 | return testFileContent; 20 | } 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/cc-visual-tests-report/visual-tests-report.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface VisualTestResult { 2 | id: string; 3 | componentTagName: string; 4 | storyName: string; 5 | viewportType: ViewportType; 6 | browserName: BrowserName; 7 | screenshots: VisualTestScreenshots; 8 | } 9 | 10 | export type BrowserName = 'chrome' | 'chromium' | 'firefox' | 'safari' | 'webkit'; 11 | 12 | export interface VisualTestScreenshots { 13 | expectationScreenshotUrl: string; 14 | diffScreenshotUrl: string; 15 | actualScreenshotUrl: string; 16 | } 17 | 18 | export type ViewportType = 'mobile' | 'desktop'; 19 | 20 | export interface VisualTestsReport { 21 | expectationMetadata: { 22 | commitReference: string; 23 | lastUpdated: string; 24 | }; 25 | actualMetadata: { 26 | commitReference: string; 27 | lastUpdated: string; 28 | }; 29 | workflowId: string; 30 | prNumber: string; 31 | branchName: string; 32 | repositoryName: string; 33 | repositoryOwner: string; 34 | impactedComponents: Array; 35 | results: VisualTestResult[]; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/cc-logs/cc-logs.types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Log { 2 | id: string; 3 | date: Date; 4 | message: string; 5 | metadata: Array; 6 | } 7 | 8 | export interface Metadata { 9 | name: string; 10 | value: string; 11 | } 12 | 13 | export type MetadataRenderer = MetadataRenderingProvider | MetadataRendering; 14 | 15 | export type MetadataRenderingProvider = (metadata: Metadata) => MetadataRendering; 16 | 17 | export interface MetadataRendering { 18 | hidden?: boolean; 19 | intent?: MetadataIntent; 20 | showName?: boolean; 21 | size?: 'auto' | number; 22 | strong?: boolean; 23 | text?: string; 24 | } 25 | 26 | export type MetadataIntent = 'neutral' | 'info' | 'success' | 'warning' | 'danger'; 27 | 28 | export interface MetadataFilter { 29 | metadata: string; 30 | value: string; 31 | } 32 | 33 | export type LogMessageFilterMode = 'loose' | 'strict' | 'regex'; 34 | 35 | export interface LogFilter { 36 | message: LogMessageFilter; 37 | metadata: Array; 38 | } 39 | 40 | export interface LogMessageFilter { 41 | type: string; 42 | value: string; 43 | } 44 | -------------------------------------------------------------------------------- /src/styles/undefined-components.css: -------------------------------------------------------------------------------- 1 | /* This is a work in progress */ 2 | 3 | cc-block:not(:defined) { 4 | border-radius: 0.25em; 5 | border: 1px solid #bcc2d1; 6 | display: grid; 7 | grid-gap: 1em; 8 | padding: 1em; 9 | } 10 | 11 | cc-block:not(:defined) > [slot='title'] { 12 | color: #3a3871; 13 | font-size: 1.2em; 14 | font-weight: 700; 15 | margin-bottom: 0.5em; 16 | } 17 | 18 | cc-block[icon]:not(:defined) > [slot='title'] { 19 | padding-left: 2.5em; 20 | } 21 | 22 | cc-input-text:not(:defined) { 23 | background: #eee; 24 | border-radius: 0.25em; 25 | display: inline-block; 26 | height: 2em; 27 | min-width: 18em; 28 | vertical-align: top; 29 | } 30 | 31 | cc-tcp-redirection-form:not(:defined) { 32 | background: #eee; 33 | border-radius: 0.25em; 34 | display: block; 35 | height: calc(3.8em - 2px); 36 | } 37 | 38 | cc-toggle:not(:defined) { 39 | background: #eee; 40 | border-radius: 0.15em; 41 | display: inline-block; 42 | height: 2em; 43 | min-width: 10em; 44 | vertical-align: top; 45 | } 46 | 47 | cc-toggle:not(:defined)::after { 48 | content: '\00a0'; 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/form/form-error-focus-controller.js: -------------------------------------------------------------------------------- 1 | import { focusFirstFormControlWithError } from './form-utils.js'; 2 | 3 | /** 4 | * @import { LitElement } from 'lit' 5 | * @import { Ref } from 'lit/directives/ref.js' 6 | */ 7 | 8 | /** 9 | * This reactive controller handles the focus after a form submission. 10 | * When there is an error, it waits for the next render and then focus the first form control with error. 11 | */ 12 | export class FormErrorFocusController { 13 | /** 14 | * 15 | * @param {LitElement} host 16 | * @param {Ref} formRef 17 | * @param {() => Object} getErrors 18 | */ 19 | constructor(host, formRef, getErrors) { 20 | this._host = host; 21 | this._host.addController(this); 22 | this._formRef = formRef; 23 | this._getErrors = getErrors; 24 | } 25 | 26 | hostUpdated() { 27 | const errors = this._getErrors(); 28 | if (errors != null && Object.values(errors).some((value) => value != null)) { 29 | this._host.updateComplete.then(() => { 30 | focusFirstFormControlWithError(this._formRef.value); 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/adr/adr-0004-why-wrap-input-textarea.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '📌 Architecture Decision Records' 3 | --- 4 | 5 | # ADR 0004: Why do we wrap `` and `` in a `div.wrapper` and puts styles on this wrapper? 6 | 7 | 🗓️ 2019-10-05 · ✍️ Hubert Sablonnière 8 | 9 | This ADR tries to explain why we wrap `` and `` in a `div.wrapper` and puts styles on this wrapper. 10 | 11 | ## Context? 12 | 13 | * We started by adding styles directly on `` and `` (including styles for states like `disabled`, `:focus`, `:hover`...). 14 | * We chose not to wrap text in mulitline `` and remove the scrollbar. 15 | 16 | ## Problems? 17 | 18 | * The behaviour of padding/margin in Chrome/Safari is not the same as Firefox when the text does not wrap. 19 | * We want the Firefox behaviour (where the margin/padding are not part of the scollable zone). 20 | 21 | ## Solution? 22 | 23 | * Move all styles to a `div.wrapper` (including `:focus` and `:hover`) 24 | * This allowed us to listen to some events directly on this wrapper 25 | * This allowed us to simplify the computation of the auto height when multiline 26 | -------------------------------------------------------------------------------- /src/assets/ram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/accessibility-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Accessibility issue 3 | about: Use this template to properly report an accessibility issue 4 | title: 'cc-xxx: accessibility issue' 5 | labels: a11y 6 | assignees: '' 7 | --- 8 | 9 | ## Accessibility issue 10 | 11 | 15 | 16 | ## Description of the issue 17 | 18 | - User Impact/Description: 19 | - WCAG Criterion: 20 | 21 | ### Context about the issue 22 | 23 | 24 | 25 | - Impacted component(s): 26 | - Browser(s): 27 | - Assistive technologies: 28 | -------------------------------------------------------------------------------- /src/components/cc-token-oauth-list/cc-token-oauth-list.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TokenOauthListState = 2 | | TokenOauthListStateLoaded 3 | | TokenOauthListStateLoading 4 | | TokenOauthListStateError 5 | | TokenOauthListStateRevokingAll; 6 | 7 | export interface TokenOauthListStateLoaded { 8 | type: 'loaded'; 9 | oauthTokens: Array; 10 | } 11 | 12 | export interface TokenOauthListStateRevokingAll { 13 | type: 'revoking-all'; 14 | oauthTokens: Array; 15 | } 16 | 17 | export interface TokenOauthListStateLoading { 18 | type: 'loading'; 19 | } 20 | 21 | export interface TokenOauthListStateError { 22 | type: 'error'; 23 | } 24 | 25 | export type OauthTokenState = OauthTokenStateIdle | OauthTokenStateRevoking; 26 | 27 | export interface OauthTokenStateIdle extends OauthToken { 28 | type: 'idle'; 29 | } 30 | 31 | export interface OauthTokenStateRevoking extends OauthToken { 32 | type: 'revoking'; 33 | } 34 | 35 | export interface OauthToken { 36 | id: string; 37 | consumerName: string; 38 | creationDate: Date; 39 | expirationDate: Date; 40 | lastUsedDate: Date; 41 | imageUrl: string; 42 | } 43 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import-x/extensions 2 | import { addons } from 'storybook/manager-api'; 3 | // eslint-disable-next-line import-x/extensions 4 | import { create } from 'storybook/theming'; 5 | import { getDocUrl } from '../src/lib/dev-hub-url.js'; 6 | import { enhanceStoryName } from '../src/stories/lib/story-names.js'; 7 | 8 | // We could create an addon to provide a control that would switch between dark / light 9 | // but it would only switch the UI theme, not the stories so right now it's not worth it 10 | const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; 11 | 12 | const cleverTheme = create({ 13 | base: isDarkMode ? 'dark' : 'light', 14 | brandTitle: 'Clever Cloud components', 15 | brandUrl: getDocUrl(), 16 | brandImage: isDarkMode ? 'imgs/logo-clever-dark.svg' : 'imgs/logo-clever-light.svg', 17 | }); 18 | 19 | addons.setConfig({ 20 | theme: cleverTheme, 21 | sidebar: { 22 | collapsedRoots: ['📖-guidelines', '🖋-copywriting', '👋-contributing', '📌-architecture-decision-records'], 23 | renderLabel: ({ name, type }) => (type === 'story' ? enhanceStoryName(name) : name), 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /cem/sort-items.js: -------------------------------------------------------------------------------- 1 | import { sortBy } from '../src/lib/utils.js'; 2 | 3 | /** 4 | * CEM analyzer plugin: sort-items 5 | * 6 | * This plugin sorts all items alphabetically by their name. 7 | * This is very useful when you want to diff different manifests. 8 | */ 9 | export default function sortItems() { 10 | return { 11 | name: 'sort-items', 12 | packageLinkPhase({ customElementsManifest }) { 13 | if (customElementsManifest.modules == null) { 14 | return; 15 | } 16 | 17 | for (const module of customElementsManifest.modules) { 18 | if (module.declarations != null) { 19 | for (const declaration of module.declarations) { 20 | declaration.attributes?.sort(sortBy('name')); 21 | declaration.cssParts?.sort(sortBy('name')); 22 | declaration.cssProperties?.sort(sortBy('name')); 23 | declaration.events?.sort(sortBy('name')); 24 | declaration.members?.sort(sortBy('name')); 25 | declaration.slots?.sort(sortBy('name')); 26 | } 27 | } 28 | } 29 | 30 | customElementsManifest.modules.sort(sortBy('path')); 31 | }, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/cc-email-list/cc-email-list.types.d.ts: -------------------------------------------------------------------------------- 1 | //# region state 2 | interface EmailListStateLoading { 3 | type: 'loading'; 4 | } 5 | 6 | interface EmailListStateError { 7 | type: 'error'; 8 | } 9 | 10 | interface EmailListStateLoaded { 11 | type: 'loaded'; 12 | emailList: EmailList; 13 | } 14 | 15 | export type EmailListState = EmailListStateLoading | EmailListStateError | EmailListStateLoaded; 16 | //#endregion 17 | 18 | //#region data model 19 | interface EmailList { 20 | primaryAddress: PrimaryAddressState; 21 | secondaryAddresses: SecondaryAddressState[]; 22 | } 23 | 24 | export interface PrimaryAddressState extends EmailAddress { 25 | type: 'idle' | 'sending-confirmation-email'; 26 | } 27 | 28 | export interface SecondaryAddressState extends EmailAddress { 29 | type: 'idle' | 'marking-as-primary' | 'deleting'; 30 | } 31 | 32 | interface EmailAddress { 33 | address: string; 34 | verified: boolean; 35 | } 36 | //#endregion 37 | 38 | export interface AddEmailFormState { 39 | type: 'idle' | 'adding'; 40 | errors?: { 41 | email: AddEmailError; 42 | }; 43 | } 44 | 45 | export type AddEmailError = 'invalid' | 'already-defined' | 'used'; 46 | -------------------------------------------------------------------------------- /src/components/cc-kv-explorer/cc-kv-explorer.smart.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '🚧 Beta/🛠 Kv Explorer/' 3 | title: '💡 Smart' 4 | --- 5 | # 💡 Smart `` 6 | 7 | ## ℹ️ Details 8 | 9 | 10 | Component <cc-kv-explorer-beta> 11 | Selector cc-kv-explorer-beta 12 | Requires auth No 13 | 14 | 15 | ## ⚙️ Params 16 | 17 | | Name | Type | Details | Default | 18 | |---------------|---------------|-------------------------------|---------| 19 | | `kvApiConfig` | `KvApiConfig` | Object with API configuration | | 20 | 21 | 22 | ```typescript 23 | 24 | interface KvApiConfig { 25 | url: string; 26 | backendUrl: string; 27 | } 28 | ``` 29 | 30 | ## 🌐 API endpoints 31 | 32 | TBD! 33 | 34 | ## ⬇️️ Examples 35 | 36 | ```html 37 | 43 | 44 | 45 | ``` 46 | -------------------------------------------------------------------------------- /cem/remove-private-members.js: -------------------------------------------------------------------------------- 1 | function isPrivate(item) { 2 | return item.name.startsWith('_'); 3 | } 4 | 5 | /** 6 | * CEM analyzer plugin: remove-private-members 7 | * 8 | * This plugin removes private fields from CEM. 9 | * It relies on the "_" prefix convention to identify private members of a class. 10 | */ 11 | export default function removePrivateMembers() { 12 | return { 13 | name: 'remove-private-members', 14 | packageLinkPhase({ customElementsManifest }) { 15 | customElementsManifest?.modules?.forEach((module) => { 16 | module?.declarations?.forEach((declaration) => { 17 | if (declaration.members != null) { 18 | declaration.members = declaration.members.filter((member) => { 19 | const propertyOrMethod = member.kind === 'field' || member.kind === 'method'; 20 | return !(propertyOrMethod && isPrivate(member)); 21 | }); 22 | } 23 | 24 | if (declaration.attributes != null) { 25 | declaration.attributes = declaration.attributes.filter((attribute) => { 26 | return !isPrivate(attribute); 27 | }); 28 | } 29 | }); 30 | }); 31 | }, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/dev-hub-url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * If you change the base URL here, you should probably also change it in places where it's still hard coded: 3 | * - README.md, 4 | * - CONTRIBUTING.md, 5 | * - sandbox/index.html, 6 | * - test/utils.test.js. 7 | */ 8 | let devHubBaseUrl = 'https://www.clever.cloud/developers'; 9 | 10 | /** @param {string} value */ 11 | export function setDevHubBaseUrl(value) { 12 | devHubBaseUrl = value.replace(/\/$/, ''); 13 | } 14 | 15 | /** 16 | * Rely on this helper for every reference to the docs website 17 | * 18 | * @param {string} [path] 19 | * @returns {string} href 20 | */ 21 | export function getDocUrl(path = '') { 22 | const docsBaseUrl = devHubBaseUrl + '/doc'; 23 | 24 | if (path === '') { 25 | return docsBaseUrl; 26 | } 27 | 28 | return path.startsWith('/') ? `${docsBaseUrl}${path}` : `${docsBaseUrl}/${path}`; 29 | } 30 | 31 | /** 32 | * Rely on this helper for every reference to the developer hub website 33 | * 34 | * @param {string} path 35 | * @returns {string} 36 | */ 37 | export function getDevHubUrl(path = '') { 38 | if (path === '') { 39 | return devHubBaseUrl; 40 | } 41 | 42 | return path.startsWith('/') ? `${devHubBaseUrl}${path}` : `${devHubBaseUrl}/${path}`; 43 | } 44 | -------------------------------------------------------------------------------- /docs/adr/adr-0001-why-do-we-wrap-button-clicks.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: '📌 Architecture Decision Records' 3 | --- 4 | 5 | # ADR 0001: Why do we wrap `` clicks in a custom event `cc-click`? 6 | 7 | 🗓️ 2019-10-01 · ✍️ Hubert Sablonnière 8 | 9 | This ADR tries to explain why we moved from just letting native clicks bubble to a custom event named `cc-click`. 10 | 11 | ## Context? 12 | 13 | We started really simple when we created the `` Web component. 14 | We tried to keep it close to standard behaviours. 15 | That's why we first decided to just let the inner native element `` fire and bubble native `click` events. 16 | 17 | ## Problems? 18 | 19 | * When the `` was used in a flexbox context (or any other situation where its height is augmented) we received native `click` events when the surrounding margin was clicked. 20 | * When the inner native `` was `disabled` we still had clicks in Chrome (maybe others). 21 | * When researching a delay mechanism (user clicks but the click is actually fired after a delay of X secs), it was not possible to achieve this in all browsers. 22 | 23 | ## Solution? 24 | 25 | * All users of this `` need to listen to `cc-click` instead of `click` and everything will be fine. 26 | -------------------------------------------------------------------------------- /src/components/cc-map/cc-map.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { Point } from '../common.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when a map marker has been clicked. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcMapMarkerClickEvent extends CcEvent { 12 | static TYPE = 'cc-map-marker-click'; 13 | 14 | /** 15 | * @param {Point} detail 16 | */ 17 | constructor(detail) { 18 | super(CcMapMarkerClickEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when a map marker has been entered by the mouse pointer. 24 | * @extends {CcEvent} 25 | */ 26 | export class CcMapMarkerEnterEvent extends CcEvent { 27 | static TYPE = 'cc-map-marker-enter'; 28 | 29 | /** 30 | * @param {Point} detail 31 | */ 32 | constructor(detail) { 33 | super(CcMapMarkerEnterEvent.TYPE, detail); 34 | } 35 | } 36 | 37 | /** 38 | * Dispatched when a map marker has been left by the mouse pointer. 39 | * @extends {CcEvent} 40 | */ 41 | export class CcMapMarkerLeaveEvent extends CcEvent { 42 | static TYPE = 'cc-map-marker-leave'; 43 | 44 | /** 45 | * @param {Point} detail 46 | */ 47 | constructor(detail) { 48 | super(CcMapMarkerLeaveEvent.TYPE, detail); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // @ts-expect-error 2 | import pluginSortImports from 'prettier-plugin-organize-imports'; 3 | import sortGetPropsPlugin from './prettier-rules/sort-lit-get-properties.js'; 4 | 5 | // When there are multiple Prettier plugins, there can be conflicts when both try to use the same language parser. 6 | // This occurs because when a language parser is re-assigned, handlers from previous plugins get lost. 7 | // Below we merge the babel parser handling of both plugins to avoid overwriting each other. 8 | // We need this hack until Prettier does something about it. 9 | // Issue: https://github.com/prettier/prettier/issues/12807 10 | /** @type {import("prettier").Parser} */ 11 | const babelParser = { 12 | ...pluginSortImports.parsers.babel, 13 | parse: sortGetPropsPlugin.parsers.babel.parse, 14 | }; 15 | 16 | /** @type {import("prettier").Parser} */ 17 | const typescriptParser = { 18 | ...pluginSortImports.parsers.typescript, 19 | }; 20 | 21 | /** @type {import("prettier").Plugin} */ 22 | const myPlugin = { 23 | parsers: { 24 | babel: babelParser, 25 | typescript: typescriptParser, 26 | }, 27 | }; 28 | 29 | export default { 30 | plugins: [myPlugin], 31 | arrowParens: 'always', 32 | printWidth: 120, 33 | tabWidth: 2, 34 | singleQuote: true, 35 | }; 36 | -------------------------------------------------------------------------------- /cem/add-github-source-in-description.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CEM analyzer plugin: add-github-source-in-description 3 | * 4 | * This plugin adds the public GitHub source in the description of each element. 5 | * It adds it at the beginning of the description, just after the first empty line. 6 | */ 7 | export default function addGithubSourceInDescription(options = {}) { 8 | const { githubProject } = options; 9 | return { 10 | name: 'add-github-source-in-description', 11 | moduleLinkPhase({ moduleDoc }) { 12 | const sourceLine = `🧐 [component's source code on GitHub](https://github.com/${githubProject}/blob/master/${moduleDoc.path})`; 13 | 14 | moduleDoc.declarations 15 | ?.filter((declaration) => declaration.kind === 'class') 16 | ?.forEach((declaration) => { 17 | const descriptionLines = declaration.description.split('\n'); 18 | const firstEmptyLineIndex = descriptionLines.findIndex((line) => line === ''); 19 | if (firstEmptyLineIndex === -1) { 20 | descriptionLines.push('', sourceLine); 21 | } else { 22 | descriptionLines.splice(firstEmptyLineIndex, 0, '', sourceLine); 23 | } 24 | declaration.description = descriptionLines.join('\n'); 25 | }); 26 | }, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/cc-env-var-form/cc-env-var-form.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { EnvVar } from '../common.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when the env var form is submitted. 9 | * @extends {CcEvent>} 10 | */ 11 | export class CcEnvVarFormSubmitEvent extends CcEvent { 12 | static TYPE = 'cc-env-var-form-submit'; 13 | 14 | /** 15 | * @param {Array} detail 16 | */ 17 | constructor(detail) { 18 | super(CcEnvVarFormSubmitEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when env changes. 24 | * @extends {CcEvent>} 25 | */ 26 | export class CcEnvChangeEvent extends CcEvent { 27 | static TYPE = 'cc-env-change'; 28 | 29 | /** 30 | * @param {Array} detail 31 | */ 32 | constructor(detail) { 33 | super(CcEnvChangeEvent.TYPE, detail); 34 | } 35 | } 36 | 37 | /** 38 | * Dispatched when env vars have been updated successfully. 39 | * @extends {CcEvent>} 40 | */ 41 | export class CcEnvVarsWasUpdatedEvent extends CcEvent { 42 | static TYPE = 'cc-env-vars-was-updated'; 43 | 44 | /** 45 | * @param {Array} detail 46 | */ 47 | constructor(detail) { 48 | super(CcEnvVarsWasUpdatedEvent.TYPE, detail); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/cc-ssh-key-list/cc-ssh-key-list.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { NewKey, GithubSshKey } from './cc-ssh-key-list.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when an ssh key creation is requested. 9 | * @extends {CcEvent} 10 | */ 11 | export class CcSshKeyCreateEvent extends CcEvent { 12 | static TYPE = 'cc-ssh-key-create'; 13 | 14 | /** 15 | * @param {NewKey} detail 16 | */ 17 | constructor(detail) { 18 | super(CcSshKeyCreateEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when an ssh key deletion is requested. 24 | * @extends {CcEvent<{name: string}>} 25 | */ 26 | export class CcSshKeyDeleteEvent extends CcEvent { 27 | static TYPE = 'cc-ssh-key-delete'; 28 | 29 | /** 30 | * @param {{name: string}} detail 31 | */ 32 | constructor(detail) { 33 | super(CcSshKeyDeleteEvent.TYPE, detail); 34 | } 35 | } 36 | 37 | /** 38 | * Dispatched when a GitHub ssh key import is requested. 39 | * @extends {CcEvent} 40 | */ 41 | export class CcSshKeyImportEvent extends CcEvent { 42 | static TYPE = 'cc-ssh-key-import'; 43 | 44 | /** 45 | * @param {GithubSshKey} detail 46 | */ 47 | constructor(detail) { 48 | super(CcSshKeyImportEvent.TYPE, detail); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/cc-env-var-input/cc-env-var-input.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * @import { EnvVarName } from './cc-env-var-input.types.js' 5 | */ 6 | 7 | /** 8 | * Dispatched when an env var value changes. 9 | * @extends {CcEvent<{name: string, value: string}>} 10 | */ 11 | export class CcEnvVarChangeEvent extends CcEvent { 12 | static TYPE = 'cc-env-var-change'; 13 | 14 | /** 15 | * @param {{name: string, value: string}} detail 16 | */ 17 | constructor(detail) { 18 | super(CcEnvVarChangeEvent.TYPE, detail); 19 | } 20 | } 21 | 22 | /** 23 | * Dispatched when an env var deletion is requested. 24 | * @extends {CcEvent} 25 | */ 26 | export class CcEnvVarDeleteEvent extends CcEvent { 27 | static TYPE = 'cc-env-var-delete'; 28 | 29 | /** 30 | * @param {EnvVarName} detail 31 | */ 32 | constructor(detail) { 33 | super(CcEnvVarDeleteEvent.TYPE, detail); 34 | } 35 | } 36 | 37 | /** 38 | * Dispatched when an env var restoration is requested. 39 | * @extends {CcEvent} 40 | */ 41 | export class CcEnvVarKeepEvent extends CcEvent { 42 | static TYPE = 'cc-env-var-keep'; 43 | 44 | /** 45 | * @param {EnvVarName} detail 46 | */ 47 | constructor(detail) { 48 | super(CcEnvVarKeepEvent.TYPE, detail); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/cc-addon-credentials-content/cc-addon-credentials-content.types.d.ts: -------------------------------------------------------------------------------- 1 | export type AddonCredential = 2 | | { 3 | code: AddonCredentialCode; 4 | value: string; 5 | } 6 | | AddonCredentialNg; 7 | 8 | export interface AddonCredentialNg { 9 | code: 'ng'; 10 | kind: 'standard' | 'multi-instances'; 11 | value: AddonCredentialNgDisabled | AddonCredentialNgDisabling | AddonCredentialNgEnabled | AddonCredentialNgEnabling; 12 | } 13 | 14 | export interface AddonCredentialNgDisabled { 15 | status: 'disabled'; 16 | } 17 | 18 | export interface AddonCredentialNgEnabled { 19 | status: 'enabled'; 20 | id: string; 21 | } 22 | 23 | export interface AddonCredentialNgDisabling { 24 | status: 'disabling'; 25 | id: string; 26 | } 27 | 28 | export interface AddonCredentialNgEnabling { 29 | status: 'enabling'; 30 | } 31 | 32 | type AddonCredentialCode = 33 | | 'user' 34 | | 'password' 35 | | 'api-client-user' 36 | | 'api-client-secret' 37 | | 'api-url' 38 | | 'api-key' 39 | | 'api-password' 40 | | 'initial-password' 41 | | 'host' 42 | | 'port' 43 | | 'token' 44 | | 'direct-host' 45 | | 'direct-port' 46 | | 'direct-uri' 47 | | 'database-name' 48 | | 'cluster-full-name' 49 | | 'uri' 50 | | 'tenant-namespace' 51 | | 'initial-user' 52 | | 'open-api-url' 53 | | 'url'; 54 | -------------------------------------------------------------------------------- /src/components/cc-invoice-list/cc-invoice-list.smart.js: -------------------------------------------------------------------------------- 1 | import { fetchAllInvoices } from '../../lib/api-helpers.js'; 2 | import { defineSmartComponent } from '../../lib/smart/define-smart-component.js'; 3 | import '../cc-smart-container/cc-smart-container.js'; 4 | import './cc-invoice-list.js'; 5 | 6 | /** 7 | * @import { CcInvoiceList } from './cc-invoice-list.js' 8 | * @import { Invoice } from '../common.types.js' 9 | * @import { OnContextUpdateArgs } from '../../lib/smart/smart-component.types.js' 10 | */ 11 | 12 | defineSmartComponent({ 13 | selector: 'cc-invoice-list', 14 | params: { 15 | apiConfig: { type: Object }, 16 | ownerId: { type: String }, 17 | }, 18 | /** 19 | * @param {OnContextUpdateArgs} args 20 | */ 21 | onContextUpdate({ context, updateComponent, signal }) { 22 | updateComponent('state', { type: 'loading' }); 23 | 24 | const { apiConfig, ownerId } = context; 25 | 26 | fetchAllInvoices({ apiConfig, ownerId, signal }) 27 | .then( 28 | /** @param {Invoice[]} invoices */ 29 | (invoices) => { 30 | updateComponent('state', { type: 'loaded', invoices }); 31 | }, 32 | ) 33 | .catch((error) => { 34 | console.error(error); 35 | updateComponent('state', { type: 'error' }); 36 | }); 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /.github/scripts/parse-version-from-tag.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | const tag = process.env.stringToParse || ''; 4 | 5 | // Strict semver validation: major.minor.patch with optional -beta.number suffix 6 | // Only allows valid semantic versions (e.g., 1.2.3 or 1.2.3-beta.4) 7 | // Prevents invalid versions like 0.0.0 from being published 8 | const match = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-beta\.(?:0|[1-9]\d*))?$/.exec(tag); 9 | 10 | let outputs = [`version=`, `prerelease=`, `skip-publish=true`]; 11 | 12 | if (match) { 13 | // Extract the prerelease part if present (e.g., "-beta.4") 14 | const prereleaseMatch = /-beta\.\d+/.exec(tag); 15 | const prereleaseType = prereleaseMatch ? prereleaseMatch[0].match(/[a-z]+/)[0] : ''; 16 | outputs = [`version=${tag}`, `prerelease=${prereleaseType}`, `skip-publish=false`]; 17 | } 18 | 19 | // Validate GITHUB_OUTPUT environment variable exists 20 | if (!process.env.GITHUB_OUTPUT) { 21 | console.error('Error: GITHUB_OUTPUT environment variable is not set'); 22 | process.exit(1); 23 | } 24 | 25 | console.log(`Parsing tag: "${tag}"`); 26 | if (match) { 27 | console.log(`✓ Valid version detected: ${tag}`); 28 | } else { 29 | console.log(`✗ Invalid version format: "${tag}" - skipping publish`); 30 | } 31 | 32 | fs.appendFileSync(process.env.GITHUB_OUTPUT, outputs.join('\n') + '\n'); 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - 'hotfix/**' 6 | name: release 7 | jobs: 8 | release: 9 | name: release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Extract branch name 13 | run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV 14 | - uses: google-github-actions/release-please-action@v3 15 | id: release 16 | with: 17 | token: ${{ secrets.CI_TOKEN }} 18 | release-type: node 19 | include-v-in-tag: false 20 | default-branch: ${{ env.BRANCH_NAME }} 21 | changelog-types: '[{"type": "feat", "section": "🚀 Features"}, {"type": "fix", "section": "🐛 Bug Fixes"}, {"type": "perf", "section": "💪 Performance Improvements"}, {"type": "deps", "section": "🗃️ Dependencies", "hidden": true}, {"type": "revert", "section": "↩ Reverts"}, {"type": "docs", "section": "📖 Documentation", "hidden": true}, {"type": "style", "section": "🎨 Styles", "hidden": true}, {"type": "chore", "section": "🧹 Miscellaneous Chores", "hidden": true}, {"type": "refactor", "section": "🛠 Code Refactoring", "hidden": true}, {"type": "test", "section": "🔬 Tests", "hidden": true}, {"type": "build", "section": "🏗️ Build System", "hidden": true}, {"type": "ci", "section": "🤖 Continuous Integration", "hidden": true}]' 22 | -------------------------------------------------------------------------------- /custom-elements-manifest.config.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import addDependenciesInDescription from './cem/add-dependencies-in-description.js'; 3 | import addGithubSourceInDescription from './cem/add-github-source-in-description.js'; 4 | import identifyReadonlyMembers from './cem/identify-readonly-members.js'; 5 | import listImages from './cem/list-images.js'; 6 | import removePrivateMembers from './cem/remove-private-members.js'; 7 | import sortItems from './cem/sort-items.js'; 8 | import supportCcEvents from './cem/support-cc-events.js'; 9 | import supportCssdisplayJsdoc from './cem/support-cssdisplay-jsdoc.js'; 10 | import supportTypedefJsdoc from './cem/support-typedef-jsdoc.js'; 11 | 12 | // Temporary for now 13 | fs.mkdirSync('dist', { recursive: true }); 14 | 15 | export default { 16 | globs: ['src/components/**/cc-*.js', 'src/lib/form/cc-form-control-element.abstract.js'], 17 | exclude: ['src/**/*.stories.js'], 18 | litelement: true, 19 | // dev: true, 20 | // watch: true, 21 | plugins: [ 22 | supportCcEvents(), 23 | sortItems(), 24 | removePrivateMembers(), 25 | identifyReadonlyMembers(), 26 | supportCssdisplayJsdoc(), 27 | supportTypedefJsdoc(), 28 | addDependenciesInDescription(), 29 | addGithubSourceInDescription({ githubProject: 'CleverCloud/clever-components' }), 30 | listImages(), 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /eslint/wc/eslint-config-wc-clever-cloud.js: -------------------------------------------------------------------------------- 1 | import wcPlugin from 'eslint-plugin-wc'; 2 | 3 | export default { 4 | name: 'wc-cc', 5 | files: ['**/*.js'], 6 | plugins: { 7 | wc: wcPlugin, 8 | }, 9 | settings: { 10 | wc: { 11 | elementBaseClasses: ['LitElement'], 12 | }, 13 | }, 14 | rules: { 15 | 'wc/no-constructor-attributes': 'error', 16 | 'wc/no-invalid-element-name': 'error', 17 | 'wc/no-self-class': 'error', 18 | 'wc/attach-shadow-constructor': 'error', 19 | 'wc/guard-super-call': 'off', 20 | 'wc/no-child-traversal-in-attributechangedcallback': 'error', 21 | 'wc/no-child-traversal-in-connectedcallback': 'error', 22 | 'wc/no-closed-shadow-root': 'error', 23 | 'wc/no-constructor-params': 'error', 24 | 'wc/no-customized-built-in-elements': 'error', 25 | 'wc/no-invalid-extends': 'error', 26 | 'wc/no-typos': 'error', 27 | 'wc/require-listener-teardown': 'error', 28 | 'wc/define-tag-after-class-definition': 'error', 29 | 'wc/expose-class-on-global': 'off', 30 | 'wc/file-name-matches-element': 'error', 31 | 'wc/guard-define-call': 'off', 32 | 'wc/max-elements-per-file': 'error', 33 | 'wc/no-constructor': 'off', 34 | 'wc/no-exports-with-element': 'off', 35 | 'wc/no-method-prefixed-with-on': 'error', 36 | 'wc/tag-name-matches-class': 'error', 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /sandbox/forms/form-demo-with-array-type.js: -------------------------------------------------------------------------------- 1 | import { css, html, LitElement } from 'lit'; 2 | import '../../src/components/cc-button/cc-button.js'; 3 | import '../../src/components/cc-input-text/cc-input-text.js'; 4 | import '../../src/components/cc-toggle/cc-toggle.js'; 5 | import { formSubmit } from '../../src/lib/form/form-submit-directive.js'; 6 | 7 | export class FormDemoWithArrayType extends LitElement { 8 | render() { 9 | return html` 10 | 11 | 12 | Names 13 | 14 | 15 | 16 | 17 | 18 | 19 | Submit 20 | 21 | `; 22 | } 23 | 24 | static get styles() { 25 | return [ 26 | // language=CSS 27 | css` 28 | :host { 29 | display: block; 30 | } 31 | 32 | form, 33 | fieldset { 34 | display: flex; 35 | flex-direction: column; 36 | gap: 0.5em; 37 | } 38 | `, 39 | ]; 40 | } 41 | } 42 | 43 | window.customElements.define('form-demo-with-array-type', FormDemoWithArrayType); 44 | -------------------------------------------------------------------------------- /src/components/cc-logs/animation-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Synchronizes an animation with `requestAnimationFrame`. 3 | */ 4 | export class AnimationRunner { 5 | /** 6 | * Starts the animation. 7 | * 8 | * The callback will be executed at each frame tick. 9 | * The callback must return whether the lastTickTimestamp should be updated. 10 | * 11 | * @param {(nowTimestamp: number, startTimestamp: number, lastTickTimestamp: number) => boolean} animationCallback 12 | */ 13 | start(animationCallback) { 14 | /** @type {(nowTimestamp: number) => void} */ 15 | this._animation = (nowTimestamp) => { 16 | const hasTicked = animationCallback(nowTimestamp, this._startTimestamp, this._lastTimestamp); 17 | if (hasTicked) { 18 | this._lastTimestamp = nowTimestamp; 19 | } 20 | if (this._animation != null) { 21 | requestAnimationFrame(this._animation); 22 | } 23 | }; 24 | 25 | this._startTimestamp = performance.now(); 26 | this._lastTimestamp = this._startTimestamp; 27 | 28 | this._animation(this._startTimestamp); 29 | } 30 | 31 | /** 32 | * @return {boolean} Whether the animation is stopped 33 | */ 34 | isStopped() { 35 | return this._animation == null; 36 | } 37 | 38 | /** 39 | * Stops the animation. 40 | */ 41 | stop() { 42 | this._animation = null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo-smart/index.js: -------------------------------------------------------------------------------- 1 | import '../src/components/cc-toaster/cc-toaster.js'; 2 | import { addTranslations, setLanguage } from '../src/lib/i18n/i18n.js'; 3 | import { updateRootContext } from '../src/lib/smart/smart-manager.js'; 4 | import { lang, translations } from '../src/translations/translations.en.js'; 5 | 6 | addTranslations(lang, translations); 7 | setLanguage(lang); 8 | 9 | updateRootContext({}); 10 | 11 | window.addEventListener('cc-notify', (event) => { 12 | document.querySelector('cc-toaster').show(event.detail); 13 | }); 14 | 15 | const { definition, ...componentProperties } = Object.fromEntries(new URL(document.location).searchParams.entries()); 16 | const componentName = definition.split('.').shift(); 17 | 18 | import(`../src/components/${componentName}/${definition}.js`); 19 | 20 | const $container = document.querySelector('cc-smart-container'); 21 | 22 | const $component = document.createElement(componentName); 23 | for (const [name, value] of Object.entries(componentProperties)) { 24 | $component.setAttribute(name, value); 25 | } 26 | $container.appendChild($component); 27 | 28 | const $contextButtons = document.querySelector('.context-buttons'); 29 | $contextButtons.addEventListener('click', (e) => { 30 | const button = e.target; 31 | if (!button.matches('button')) { 32 | return; 33 | } 34 | $container.context = JSON.parse(button.dataset.context); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/cc-logs-loading-progress/cc-logs-loading-progress.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when pause of the logs stream is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcLogsLoadingPauseEvent extends CcEvent { 8 | static TYPE = 'cc-logs-loading-pause'; 9 | 10 | constructor() { 11 | super(CcLogsLoadingPauseEvent.TYPE); 12 | } 13 | } 14 | 15 | /** 16 | * Dispatched when resume of the logs stream is requested. 17 | * @extends {CcEvent} 18 | */ 19 | export class CcLogsLoadingResumeEvent extends CcEvent { 20 | static TYPE = 'cc-logs-loading-resume'; 21 | 22 | constructor() { 23 | super(CcLogsLoadingResumeEvent.TYPE); 24 | } 25 | } 26 | 27 | /** 28 | * Dispatched when the logs stream overflow is accepted. 29 | * @extends {CcEvent} 30 | */ 31 | export class CcLogsLoadingOverflowAcceptEvent extends CcEvent { 32 | static TYPE = 'cc-logs-loading-overflow-accept'; 33 | 34 | constructor() { 35 | super(CcLogsLoadingOverflowAcceptEvent.TYPE); 36 | } 37 | } 38 | 39 | /** 40 | * Dispatched when the logs stream overflow is discarded. 41 | * @extends {CcEvent} 42 | */ 43 | export class CcLogsLoadingOverflowDiscardEvent extends CcEvent { 44 | static TYPE = 'cc-logs-loading-overflow-discard'; 45 | 46 | constructor() { 47 | super(CcLogsLoadingOverflowDiscardEvent.TYPE); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/stories/lib/i18n-control.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import-x/extensions 2 | import { UPDATE_GLOBALS } from 'storybook/internal/core-events'; 3 | // eslint-disable-next-line import-x/extensions 4 | import { addons } from 'storybook/preview-api'; 5 | import { addTranslations, getLanguage, setLanguage } from '../../lib/i18n/i18n.js'; 6 | import * as en from '../../translations/translations.en.js'; 7 | import * as fr from '../../translations/translations.fr.js'; 8 | 9 | const availableLanguages = [ 10 | { value: 'en', title: 'English' }, 11 | { value: 'fr', title: 'Français' }, 12 | { value: 'missing', title: '🤬 Missing' }, 13 | ]; 14 | 15 | // Init languages 16 | addTranslations(en.lang, en.translations); 17 | addTranslations(fr.lang, fr.translations); 18 | 19 | const INIT_LANG = window.localStorage.getItem('I18N_LANG') ?? 'en'; 20 | 21 | // Default to English 22 | setLanguage(INIT_LANG); 23 | 24 | const ALL_LANGS = availableLanguages.map((o) => o.value); 25 | 26 | window.addEventListener('keypress', ({ key, altKey, ctrlKey, metaKey, shiftKey }) => { 27 | if (key === 'i' && !altKey && !ctrlKey && !metaKey && !shiftKey) { 28 | const langIdx = ALL_LANGS.indexOf(getLanguage()); 29 | const nextIdx = (langIdx + 1) % ALL_LANGS.length; 30 | const nextLang = ALL_LANGS[nextIdx]; 31 | addons.getChannel().emit(UPDATE_GLOBALS, { globals: { locale: nextLang } }); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/lib/form/form.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../events.js'; 2 | 3 | /** 4 | * @import { FormValidity, FormDataMap as FormSubmittedData } from './form.types.js' 5 | * @import { ErrorMessage } from './validation.types.js' 6 | */ 7 | 8 | /** 9 | * Dispatched when a form is submitted with invalid state. 10 | * @extends {CcEvent} 11 | */ 12 | export class CcFormInvalidEvent extends CcEvent { 13 | static TYPE = 'cc-form-invalid'; 14 | 15 | /** 16 | * @param {FormValidity} detail 17 | */ 18 | constructor(detail) { 19 | super(CcFormInvalidEvent.TYPE, detail); 20 | } 21 | } 22 | 23 | /** 24 | * Dispatched when a form is submitted with valid state. 25 | * @extends {CcEvent} 26 | */ 27 | export class CcFormValidEvent extends CcEvent { 28 | static TYPE = 'cc-form-valid'; 29 | 30 | /** 31 | * @param {FormSubmittedData} detail 32 | */ 33 | constructor(detail) { 34 | super(CcFormValidEvent.TYPE, detail); 35 | } 36 | } 37 | 38 | /** 39 | * Dispatched when the `errorMessage` property of a form control element changes. 40 | * @extends {CcEvent} 41 | */ 42 | export class CcErrorMessageChangeEvent extends CcEvent { 43 | static TYPE = 'cc-error-message-change'; 44 | 45 | /** 46 | * @param {ErrorMessage|null} detail 47 | */ 48 | constructor(detail) { 49 | super(CcErrorMessageChangeEvent.TYPE, detail); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/leaflet/leaflet-esm.js: -------------------------------------------------------------------------------- 1 | // @ts-no-check 2 | /* 3 | 4 | We want to use the ESM version of Leaflet so we can treeshake what we don't use. 5 | When we tried that, we discovered that some parts of the code we need were removed by the treeshaking process. 6 | 7 | We did a lot of experiments and we discovered that some modules inside Leaflet have side effects. 8 | 9 | Take a look at `node_modules/leaflet/src/layer/Tooltip.js` for example. 10 | You will notice that this module exports the `Tooltip` class and the `tooltip` function. 11 | But below that, there are a few calls that have side effects. 12 | Calling `SomeClass.include()` actually patch `SomeClass` to add new methods. 13 | This is described in `node_modules/leaflet/src/core/Class.js`. 14 | The class `Class` has other methods like this one that will patch the original class. 15 | 16 | In our use case, it seems like we're missing: 17 | 18 | * zoom controls 19 | * tooltips 20 | * the `getRenderer` function but we're not sure why 21 | 22 | This is why we import them explicitly here. 23 | This is also why we have special rules in `rollup/rollup-common.js` at `treeshakeOptions` for those files. 24 | 25 | */ 26 | 27 | import 'leaflet/src/control/index.js'; 28 | import 'leaflet/src/layer/Tooltip.js'; 29 | import 'leaflet/src/layer/vector/Renderer.getRenderer.js'; 30 | 31 | export * from 'leaflet/src/Leaflet.js'; 32 | export { HeatLayer } from './leaflet-heat.js'; 33 | -------------------------------------------------------------------------------- /src/components/cc-logs-date-range-selector/date-range-selection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { LogsDateRangeSelection } from './cc-logs-date-range-selector.types.js' 3 | * @import { DateRange } from '../../lib/date/date-range.types.js' 4 | */ 5 | 6 | import { getRangeToNow, lastXDays, today, yesterday } from '../../lib/date/date-range-utils.js'; 7 | 8 | /** 9 | * Returns the date range corresponding to the given selection. 10 | * 11 | * Note that a `live` selection gives à 10 minutes window until now. 12 | * 13 | * @param {LogsDateRangeSelection} dateRangeSelection 14 | * @return {DateRange} 15 | */ 16 | export function dateRangeSelectionToDateRange(dateRangeSelection) { 17 | switch (dateRangeSelection.type) { 18 | case 'custom': 19 | return { 20 | since: dateRangeSelection.since, 21 | until: dateRangeSelection.until, 22 | }; 23 | case 'live': 24 | return { 25 | since: getRangeToNow(1000 * 60 * 10).since, 26 | }; 27 | case 'preset': 28 | switch (dateRangeSelection.preset) { 29 | case 'lastHour': 30 | return getRangeToNow(1000 * 60 * 60); 31 | case 'last4Hours': 32 | return getRangeToNow(1000 * 60 * 60 * 4); 33 | case 'last7Days': 34 | return lastXDays(7); 35 | case 'today': 36 | return today(); 37 | case 'yesterday': 38 | return yesterday(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/cc-visual-tests-report-menu/cc-visual-tests-report-menu.stories.js: -------------------------------------------------------------------------------- 1 | import { visualTestsResults } from '../../stories/fixtures/visual-tests-results.js'; 2 | import { makeStory } from '../../stories/lib/make-story.js'; 3 | import './cc-visual-tests-report-menu.js'; 4 | 5 | export default { 6 | tags: ['autodocs'], 7 | title: '🧐 Visual tests/', 8 | component: 'cc-visual-tests-report-menu', 9 | }; 10 | 11 | const conf = { 12 | component: 'cc-visual-tests-report-menu', 13 | css: ` 14 | :host { 15 | max-width: 100% !important; 16 | } 17 | 18 | cc-visual-tests-report-menu { 19 | max-width: 20em; 20 | border-right: solid 1px var(--cc-color-border-neutral-weak); 21 | background-color: var(--cc-color-bg-neutral); 22 | } 23 | `, 24 | }; 25 | 26 | /** 27 | * @import { CcVisualTestsReportMenu } from './cc-visual-tests-report-menu.js' 28 | */ 29 | 30 | export const defaultStory = makeStory(conf, { 31 | /** @type {Array>} */ 32 | items: [ 33 | { 34 | testsResults: visualTestsResults, 35 | }, 36 | ], 37 | }); 38 | 39 | export const preselectedMenuEntry = makeStory(conf, { 40 | /** @type {Array>} */ 41 | items: [ 42 | { 43 | testsResults: visualTestsResults, 44 | activeTestResultId: visualTestsResults[1].id, 45 | }, 46 | ], 47 | }); 48 | -------------------------------------------------------------------------------- /src/lib/dom.js: -------------------------------------------------------------------------------- 1 | const timeoutCache = new WeakMap(); 2 | 3 | /** 4 | * @param {Element} parent 5 | * @param {Element} child 6 | */ 7 | export function scrollChildIntoParent(parent, child) { 8 | const oldTimeoutId = timeoutCache.get(parent); 9 | clearTimeout(oldTimeoutId); 10 | 11 | const newTimeoutId = setTimeout(() => { 12 | if (child == null) { 13 | return; 14 | } 15 | doScrollChildIntoParent(parent, child); 16 | }, 200); 17 | 18 | timeoutCache.set(parent, newTimeoutId); 19 | } 20 | 21 | /** 22 | * @param {Element} parent 23 | * @param {Element} child 24 | */ 25 | function doScrollChildIntoParent(parent, child) { 26 | // In our situation, we don't need to handle borders and paddings 27 | const parentRect = parent.getBoundingClientRect(); 28 | const childRect = child.getBoundingClientRect(); 29 | 30 | if (childRect.top < parentRect.top) { 31 | const top = childRect.top - parentRect.top; 32 | parent.scrollBy({ top, behavior: 'smooth' }); 33 | } else if (childRect.bottom > parentRect.bottom && childRect.top > parentRect.top) { 34 | const top = childRect.bottom - parentRect.bottom; 35 | parent.scrollBy({ top, behavior: 'smooth' }); 36 | } 37 | } 38 | 39 | /** 40 | * @param {HTMLElement} element 41 | * @param {string} clazz 42 | * @return {boolean} 43 | */ 44 | export function hasClass(element, clazz) { 45 | return element.classList?.contains(clazz) ?? false; 46 | } 47 | -------------------------------------------------------------------------------- /src/components/cc-kv-list-input/cc-kv-list-input.stories.js: -------------------------------------------------------------------------------- 1 | import { makeStory } from '../../stories/lib/make-story.js'; 2 | import './cc-kv-list-input.js'; 3 | 4 | export default { 5 | tags: ['autodocs'], 6 | title: '🚧 Beta/🛠 Kv Explorer/', 7 | component: 'cc-kv-list-input-beta', 8 | }; 9 | 10 | /** 11 | * @import { CcKvListInput } from './cc-kv-list-input.js' 12 | */ 13 | 14 | const conf = { 15 | component: 'cc-kv-list-input-beta', 16 | beta: true, 17 | }; 18 | 19 | export const defaultStory = makeStory(conf, { 20 | /** @type {Array>} */ 21 | items: [ 22 | { 23 | value: ['first value', 'second value', 'third value', 'fourth value'], 24 | }, 25 | ], 26 | }); 27 | 28 | export const withEmptyValue = makeStory(conf, { 29 | /** @type {Array>} */ 30 | items: [ 31 | { 32 | value: [], 33 | }, 34 | ], 35 | }); 36 | 37 | export const disabled = makeStory(conf, { 38 | /** @type {Array>} */ 39 | items: [ 40 | { 41 | value: ['first value', 'second value', 'third value', 'fourth value'], 42 | disabled: true, 43 | }, 44 | ], 45 | }); 46 | 47 | export const readonly = makeStory(conf, { 48 | /** @type {Array>} */ 49 | items: [ 50 | { 51 | value: ['first value', 'second value', 'third value', 'fourth value'], 52 | readonly: true, 53 | }, 54 | ], 55 | }); 56 | -------------------------------------------------------------------------------- /src/lib/zone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { Zone } from '../components/common.types.js' 3 | */ 4 | 5 | const CLEVER_CLOUD_ZONE = 'infra:clever-cloud'; 6 | export const PRIVATE_ZONE = 'scope:private'; 7 | 8 | /** 9 | * Sort zones as follows: 10 | * 1. Clever Cloud zones "infra:clever-cloud" 11 | * 2. Private zones "scope:private" 12 | * 3. Alphanum sort on city 13 | * 14 | * @template {Zone} T 15 | * @param {T[]} rawZones 16 | * @returns {T[]} 17 | */ 18 | export function sortZones(rawZones) { 19 | if (rawZones == null) { 20 | return null; 21 | } 22 | return [...rawZones].sort((a, b) => { 23 | if (a == null || b == null) { 24 | return 0; 25 | } 26 | const aIsCleverCloud = a.tags.includes(CLEVER_CLOUD_ZONE); 27 | const bIsCleverCloud = b.tags.includes(CLEVER_CLOUD_ZONE); 28 | if (aIsCleverCloud !== bIsCleverCloud) { 29 | return aIsCleverCloud ? -1 : 1; 30 | } 31 | const aIsPrivate = a.tags.includes(PRIVATE_ZONE); 32 | const bIsPrivate = b.tags.includes(PRIVATE_ZONE); 33 | if (aIsCleverCloud && bIsCleverCloud) { 34 | if (aIsPrivate !== bIsPrivate) { 35 | return aIsPrivate ? 1 : -1; 36 | } 37 | if (aIsPrivate && bIsPrivate) { 38 | return (a.displayName ?? '').localeCompare(b.displayName ?? ''); 39 | } 40 | } 41 | if (aIsPrivate !== bIsPrivate) { 42 | return aIsPrivate ? -1 : 1; 43 | } 44 | return a.city.localeCompare(b.city); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/components/cc-token-api-list/cc-token-api-list.types.d.ts: -------------------------------------------------------------------------------- 1 | export type TokenApiListState = 2 | | TokenApiListStateLoading 3 | | TokenApiListStateError 4 | | TokenApiListStateLoaded 5 | | TokenApiListStateNoPassword 6 | | TokenApiListStateResettingPassword; 7 | 8 | export interface TokenApiListStateLoading { 9 | type: 'loading'; 10 | } 11 | 12 | export interface TokenApiListStateError { 13 | type: 'error'; 14 | } 15 | 16 | export interface TokenApiListStateLoaded { 17 | type: 'loaded'; 18 | apiTokens: ApiTokenState[]; 19 | } 20 | 21 | export interface TokenApiListStateNoPassword { 22 | type: 'no-password'; 23 | } 24 | 25 | export interface TokenApiListStateResettingPassword { 26 | type: 'resetting-password'; 27 | } 28 | 29 | export type ApiTokenState = ApiTokenStateIdle | ApiTokenStateRevoking; 30 | 31 | export interface ApiTokenStateIdle extends ApiToken { 32 | type: 'idle'; 33 | } 34 | 35 | export interface ApiTokenStateRevoking extends ApiToken { 36 | type: 'revoking'; 37 | } 38 | 39 | export interface ApiToken { 40 | id: string; 41 | creationDate: Date; 42 | expirationDate: Date; 43 | name: string; 44 | description?: string; 45 | isExpired: boolean; 46 | } 47 | 48 | export interface RawApiToken { 49 | apiTokenId: string; 50 | userId: string; 51 | creationDate: string; // ISO 52 | expirationDate: string; // ISO 53 | ip: string; 54 | name: string; 55 | description?: string; 56 | state: 'ACTIVE' | 'EXPIRED'; 57 | } 58 | -------------------------------------------------------------------------------- /src/components/cc-logs-instances/cc-logs-instances.types.d.ts: -------------------------------------------------------------------------------- 1 | export type LogsInstancesState = LogsInstancesStateLoading | LogsInstancesStateError | LogsInstancesStateLoaded; 2 | 3 | export interface LogsInstancesStateLoading { 4 | state: 'loading'; 5 | } 6 | 7 | export interface LogsInstancesStateError { 8 | state: 'error'; 9 | } 10 | 11 | export interface LogsInstancesStateLoaded { 12 | state: 'loaded'; 13 | mode: LogsMode; 14 | instances: Array; 15 | selection: Array; 16 | } 17 | 18 | export type LogsMode = 'live' | 'cold'; 19 | 20 | export type DeploymentState = 'QUEUED' | 'WORK_IN_PROGRESS' | 'SUCCEEDED' | 'CANCELLED' | 'FAILED'; 21 | 22 | export interface Deployment { 23 | id: string; 24 | state: DeploymentState; 25 | creationDate: Date; 26 | commitId: string; 27 | endDate?: Date; 28 | } 29 | 30 | export type InstanceState = 31 | | 'BOOTING' 32 | | 'STARTING' 33 | | 'DEPLOYING' 34 | | 'BUILDING' 35 | | 'READY' 36 | | 'UP' 37 | | 'STOPPING' 38 | | 'DELETED' 39 | | 'MIGRATION_IN_PROGRESS' 40 | | 'TASK_IN_PROGRESS'; 41 | export type InstanceKind = 'BUILD' | 'RUN'; 42 | 43 | export interface Instance { 44 | ghost: false; 45 | id: string; 46 | name: string; 47 | index: number; 48 | deployment: Deployment; 49 | state: InstanceState; 50 | creationDate: Date; 51 | deletionDate?: Date; 52 | kind: InstanceKind; 53 | } 54 | 55 | export interface GhostInstance { 56 | ghost: true; 57 | id: string; 58 | } 59 | -------------------------------------------------------------------------------- /cem/add-dependencies-in-description.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CEM analyzer plugin: add-dependencies-in-description 3 | * 4 | * This plugin adds the dependencies and the dependants at the description of each component if they have some. 5 | */ 6 | import { getComponentsGraph, getComponentsTree } from '../tasks/component-usage-graph.js'; 7 | 8 | const graph = await getComponentsGraph(); 9 | 10 | export default function addDependenciesInDescription() { 11 | return { 12 | name: 'add-dependencies-in-description', 13 | moduleLinkPhase({ moduleDoc }) { 14 | const isComponent = moduleDoc.path.includes('src/components/'); 15 | const isSmartComponent = moduleDoc.path.includes('.smart'); 16 | 17 | const shouldProcess = isComponent && !isSmartComponent; 18 | const dependencies = shouldProcess ? getComponentsTree([moduleDoc.path], graph, 'dependencies') : ''; 19 | const dependants = shouldProcess ? getComponentsTree([moduleDoc.path], graph, 'dependants', 1) : ''; 20 | 21 | let sourceLine = ''; 22 | if (dependencies !== '') { 23 | sourceLine += '### Dependencies\n' + '```\n' + dependencies + '\n```'; 24 | } 25 | if (dependants !== '') { 26 | sourceLine += '\n\n### Dependants\n' + '```\n' + dependants + '\n```'; 27 | } 28 | 29 | moduleDoc.declarations 30 | ?.filter((declaration) => declaration.kind === 'class') 31 | ?.forEach((declaration) => { 32 | declaration.description += '\n\n' + sourceLine; 33 | }); 34 | }, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/cc-visual-tests-report-entry/cc-visual-tests-report-entry.stories.js: -------------------------------------------------------------------------------- 1 | import { visualTestsResults } from '../../stories/fixtures/visual-tests-results.js'; 2 | import { makeStory } from '../../stories/lib/make-story.js'; 3 | import './cc-visual-tests-report-entry.js'; 4 | 5 | export default { 6 | tags: ['autodocs'], 7 | title: '🧐 Visual tests/', 8 | component: 'cc-visual-tests-report-entry', 9 | }; 10 | 11 | const conf = { 12 | component: 'cc-visual-tests-report-entry', 13 | css: ` 14 | :host { 15 | max-width: 100% !important; 16 | } 17 | `, 18 | }; 19 | 20 | /** 21 | * @import { CcVisualTestsReportEntry } from './cc-visual-tests-report-entry.js' 22 | */ 23 | 24 | const visualTestResult = visualTestsResults.find(({ id }) => id === 'cc-article-list-data-loaded-desktop-chromium'); 25 | 26 | export const defaultStory = makeStory(conf, { 27 | /** @type {Partial[]} */ 28 | items: [ 29 | { 30 | testResult: visualTestResult, 31 | }, 32 | ], 33 | }); 34 | 35 | export const comparison = makeStory(conf, { 36 | /** @type {Partial[]} */ 37 | items: [ 38 | { 39 | testResult: visualTestResult, 40 | viewerMode: 'comparison', 41 | }, 42 | ], 43 | }); 44 | 45 | export const diff = makeStory(conf, { 46 | /** @type {Partial[]} */ 47 | items: [ 48 | { 49 | testResult: visualTestResult, 50 | viewerMode: 'diff', 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/cc-kv-set-explorer/cc-kv-set-explorer.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when more KV set elements loading is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcKvSetLoadMoreEvent extends CcEvent { 8 | static TYPE = 'cc-kv-set-load-more'; 9 | 10 | constructor() { 11 | super(CcKvSetLoadMoreEvent.TYPE); 12 | } 13 | } 14 | 15 | /** 16 | * Dispatched when a KV set element deletion is requested. 17 | * @extends {CcEvent} 18 | */ 19 | export class CcKvSetElementDeleteEvent extends CcEvent { 20 | static TYPE = 'cc-kv-set-element-delete'; 21 | 22 | /** 23 | * @param {string} detail 24 | */ 25 | constructor(detail) { 26 | super(CcKvSetElementDeleteEvent.TYPE, detail); 27 | } 28 | } 29 | 30 | /** 31 | * Dispatched when KV set elements filter changes. 32 | * @extends {CcEvent} 33 | */ 34 | export class CcKvSetFilterChangeEvent extends CcEvent { 35 | static TYPE = 'cc-kv-set-filter-change'; 36 | 37 | /** 38 | * @param {string} detail 39 | */ 40 | constructor(detail) { 41 | super(CcKvSetFilterChangeEvent.TYPE, detail); 42 | } 43 | } 44 | 45 | /** 46 | * Dispatched when a KV set element creation is requested. 47 | * @extends {CcEvent} 48 | */ 49 | export class CcKvSetElementAddEvent extends CcEvent { 50 | static TYPE = 'cc-kv-set-element-add'; 51 | 52 | /** 53 | * @param {string} detail 54 | */ 55 | constructor(detail) { 56 | super(CcKvSetElementAddEvent.TYPE, detail); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /eslint/i18n/custom-rules/i18n-valid-key.js: -------------------------------------------------------------------------------- 1 | import { getTranslationProperties, isMainTranslationNode, isTranslationFile } from './i18n-shared.js'; 2 | 3 | const VALID_TRANSLATION_KEY = /^[a-z]+-[a-z][a-z-]*\.[a-z0-9-.]+$/; 4 | 5 | function report(context, key, node) { 6 | context.report({ 7 | node, 8 | messageId: 'unexpectedTranslationKey', 9 | data: { key }, 10 | }); 11 | } 12 | 13 | /** @type {import('eslint').Rule.RuleModule} */ 14 | export default { 15 | meta: { 16 | type: 'suggestion', 17 | docs: { 18 | description: 'enforce naming pattern on translation keys', 19 | category: 'Translation files', 20 | }, 21 | fixable: 'code', 22 | messages: { 23 | unexpectedTranslationKey: 'Unexpected translation key pattern: {{key}}', 24 | }, 25 | }, 26 | create: function (context) { 27 | // Early return for non translation files 28 | if (!isTranslationFile(context)) { 29 | return {}; 30 | } 31 | 32 | return { 33 | ExportNamedDeclaration(node) { 34 | // Early return for nodes that aren't the one exporting translations 35 | if (!isMainTranslationNode(node)) { 36 | return; 37 | } 38 | 39 | const translationProperties = getTranslationProperties(node); 40 | 41 | for (const tp of translationProperties) { 42 | const key = tp.key.value; 43 | 44 | if (key == null || !key.match(VALID_TRANSLATION_KEY)) { 45 | report(context, key, tp.key); 46 | } 47 | } 48 | }, 49 | }; 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /test/i18n/i18n-string.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from '@bundled-es-modules/chai'; 2 | import { preparePlural } from '../../src/lib/i18n/i18n-string.js'; 3 | 4 | describe('preparePlural', () => { 5 | describe('english', () => { 6 | const plural = preparePlural('en'); 7 | 8 | it('select correct rule', () => { 9 | expect(plural(0, 'mouse', 'mice')).to.equal('mice'); 10 | expect(plural(1, 'mouse', 'mice')).to.equal('mouse'); 11 | expect(plural(2, 'mouse', 'mice')).to.equal('mice'); 12 | expect(plural(10, 'mouse', 'mice')).to.equal('mice'); 13 | }); 14 | 15 | it('automatic "s" suffix', () => { 16 | expect(plural(0, 'cat')).to.equal('cats'); 17 | expect(plural(1, 'cat')).to.equal('cat'); 18 | expect(plural(2, 'cat')).to.equal('cats'); 19 | expect(plural(10, 'cat')).to.equal('cats'); 20 | }); 21 | }); 22 | 23 | describe('french', () => { 24 | const plural = preparePlural('fr'); 25 | 26 | it('select correct rule', () => { 27 | expect(plural(0, 'cheval', 'chevaux')).to.equal('cheval'); 28 | expect(plural(1, 'cheval', 'chevaux')).to.equal('cheval'); 29 | expect(plural(2, 'cheval', 'chevaux')).to.equal('chevaux'); 30 | expect(plural(10, 'cheval', 'chevaux')).to.equal('chevaux'); 31 | }); 32 | 33 | it('automatic "s" suffix', () => { 34 | expect(plural(0, 'chat')).to.equal('chat'); 35 | expect(plural(1, 'chat')).to.equal('chat'); 36 | expect(plural(2, 'chat')).to.equal('chats'); 37 | expect(plural(10, 'chat')).to.equal('chats'); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/components/cc-header-app/cc-header-app.events.js: -------------------------------------------------------------------------------- 1 | import { CcEvent } from '../../lib/events.js'; 2 | 3 | /** 4 | * Dispatched when a deployment cancellation is requested. 5 | * @extends {CcEvent} 6 | */ 7 | export class CcDeploymentCancelEvent extends CcEvent { 8 | static TYPE = 'cc-deployment-cancel'; 9 | 10 | constructor() { 11 | super(CcDeploymentCancelEvent.TYPE); 12 | } 13 | } 14 | 15 | /** 16 | * Dispatch when an application restart is requested. 17 | * @extends {CcEvent<'normal'|'rebuild'|'last-commit'>} 18 | */ 19 | export class CcApplicationRestartEvent extends CcEvent { 20 | static TYPE = 'cc-application-restart'; 21 | 22 | /** 23 | * @param {'normal'|'rebuild'|'last-commit'} [detail='normal'] 24 | */ 25 | constructor(detail) { 26 | super(CcApplicationRestartEvent.TYPE, detail ?? 'normal'); 27 | } 28 | } 29 | 30 | /** 31 | * Dispatch when an application start is requested. 32 | * @extends {CcEvent<'normal'|'rebuild'|'last-commit'>} 33 | */ 34 | export class CcApplicationStartEvent extends CcEvent { 35 | static TYPE = 'cc-application-start'; 36 | 37 | /** 38 | * @param {'normal'|'rebuild'|'last-commit'} [detail='normal'] 39 | */ 40 | constructor(detail) { 41 | super(CcApplicationStartEvent.TYPE, detail ?? 'normal'); 42 | } 43 | } 44 | 45 | /** 46 | * Dispatch when an application stop is requested. 47 | * @extends {CcEvent} 48 | */ 49 | export class CcApplicationStopEvent extends CcEvent { 50 | static TYPE = 'cc-application-stop'; 51 | 52 | constructor() { 53 | super(CcApplicationStopEvent.TYPE); 54 | } 55 | } 56 | --------------------------------------------------------------------------------
<cc-kv-explorer-beta>
cc-kv-explorer-beta