diff --git a/.github/workflows/release-rancher-epinio-standalone.yml b/.github/workflows/release-rancher-epinio-standalone.yml index aca54f4602..24c32f6ad6 100644 --- a/.github/workflows/release-rancher-epinio-standalone.yml +++ b/.github/workflows/release-rancher-epinio-standalone.yml @@ -25,7 +25,7 @@ jobs: - name: Install & Build run: - RANCHER_ENV=epinio ./.github/workflows/scripts/build-dashboard.sh + RANCHER_ENV=epinio EXCLUDES_PKG=rancher-components ./.github/workflows/scripts/build-dashboard.sh - name: Upload Build uses: actions/upload-artifact@v2 diff --git a/.github/workflows/scripts/build-dashboard.sh b/.github/workflows/scripts/build-dashboard.sh index 88609b02ec..59cfba3b9a 100755 --- a/.github/workflows/scripts/build-dashboard.sh +++ b/.github/workflows/scripts/build-dashboard.sh @@ -4,7 +4,9 @@ set -e echo "GITHUB_SHA: $GITHUB_SHA" echo "GITHUB_REF_NAME: $GITHUB_REF_NAME" echo "ROUTER_BASE: $ROUTER_BASE" +echo echo "RANCHER_ENV: $RANCHER_ENV" +echo "EXCLUDES_PKG: $EXCLUDES_PKG" echo echo "RELEASE_DIR: $RELEASE_DIR" RELEASE_LOCATION="$RELEASE_DIR/$ARTIFACT_NAME" @@ -26,7 +28,7 @@ echo Installing dependencies yarn install --frozen-lockfile echo Building -NUXT_ENV_commit=$GITHUB_SHA NUXT_ENV_version=$GITHUB_REF_NAME OUTPUT_DIR="$ARTIFACT_LOCATION" ROUTER_BASE="$ROUTER_BASE" RANCHER_ENV=$RANCHER_ENV API=$API RESOURCE_BASE=$RESOURCE_BASE yarn run build --spa +NUXT_ENV_commit=$GITHUB_SHA NUXT_ENV_version=$GITHUB_REF_NAME OUTPUT_DIR="$ARTIFACT_LOCATION" ROUTER_BASE="$ROUTER_BASE" RANCHER_ENV=$RANCHER_ENV API=$API RESOURCE_BASE=$RESOURCE_BASE EXCLUDES_PKG=$EXCLUDES_PKG yarn run build --spa echo Creating tar tar -czf $RELEASE_LOCATION.tar.gz -C $ARTIFACT_LOCATION . diff --git a/pkg/epinio/components/EpinioIntro.vue b/pkg/epinio/components/EpinioIntro.vue new file mode 100644 index 0000000000..11bf57a6ae --- /dev/null +++ b/pkg/epinio/components/EpinioIntro.vue @@ -0,0 +1,57 @@ + + + diff --git a/pkg/epinio/components/application/AppInfo.vue b/pkg/epinio/components/application/AppInfo.vue index ab2fd4fa66..6469c9a9e8 100644 --- a/pkg/epinio/components/application/AppInfo.vue +++ b/pkg/epinio/components/application/AppInfo.vue @@ -5,10 +5,11 @@ import NameNsDescription from '@shell/components/form/NameNsDescription.vue'; import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue'; import KeyValue from '@shell/components/form/KeyValue.vue'; import ArrayList from '@shell/components/form/ArrayList.vue'; -import Banner from '@components/Banner/Banner.vue'; +import Loading from '@shell/components/Loading.vue'; import { EPINIO_TYPES } from '../../types'; import { sortBy } from '@shell/utils/sort'; +import { validateKubernetesName } from '@shell/utils/validators/kubernetes-name'; export interface EpinioAppInfo { meta: { @@ -24,7 +25,7 @@ export interface EpinioAppInfo { interface Data { errors: string[], - values: EpinioAppInfo + values?: EpinioAppInfo } // Data, Methods, Computed, Props @@ -35,7 +36,7 @@ export default Vue.extend({ NameNsDescription, LabeledInput, KeyValue, - Banner + Loading }, props: { @@ -52,17 +53,7 @@ export default Vue.extend({ data() { return { errors: [], - values: { - meta: { - name: this.application.meta?.name, - namespace: this.application.meta?.namespace - }, - configuration: { - instances: this.application.configuration?.instances || 1, - environment: this.application.configuration?.environment || {}, - routes: this.application.configuration?.routes || [], - }, - } + values: undefined }; }, @@ -70,7 +61,7 @@ export default Vue.extend({ this.values = { meta: { name: this.application.meta?.name, - namespace: this.application.meta?.namespace + namespace: this.application.meta?.namespace || this.namespaces[0]?.metadata.name }, configuration: { instances: this.application.configuration?.instances || 1, @@ -105,8 +96,13 @@ export default Vue.extend({ }, valid() { + if (!this.values) { + return false; + } const validName = !!this.values.meta?.name; - const validNamespace = !!this.values.meta?.namespace; + + const nsErrors = validateKubernetesName(this.values.meta?.namespace || '', '', this.$store.getters, undefined, []); + const validNamespace = nsErrors.length === 0; const validInstances = typeof this.values.configuration?.instances !== 'string' && this.values.configuration?.instances >= 0; return validName && validNamespace && validInstances; @@ -127,11 +123,7 @@ export default Vue.extend({ - + @@ -423,6 +466,10 @@ export default Vue.extend({ margin-left: 5px; } } + + .collapse { + margin-left: -5px; + } } .archive { display: flex; diff --git a/pkg/epinio/config/epinio.ts b/pkg/epinio/config/epinio.ts index 95f13fdeff..f59e5fe456 100644 --- a/pkg/epinio/config/epinio.ts +++ b/pkg/epinio/config/epinio.ts @@ -71,7 +71,6 @@ export function init($plugin: any, store: any) { configureType(EPINIO_TYPES.INSTANCE, { customRoute: createEpinioRoute('c-cluster-resource', { resource: EPINIO_TYPES.INSTANCE }) }); // App resource - weightType(EPINIO_TYPES.APP, 300, true); configureType(EPINIO_TYPES.APP, { isCreatable: true, isEditable: true, @@ -81,8 +80,17 @@ export function init($plugin: any, store: any) { customRoute: createEpinioRoute('c-cluster-applications', { }), }); + // App Chart resource + configureType(EPINIO_TYPES.APP_CHARTS, { + isCreatable: false, + isEditable: false, + isRemovable: false, + showState: false, + canYaml: false, + customRoute: createEpinioRoute('c-cluster-resource', { resource: EPINIO_TYPES.APP_CHARTS }), + }); + // Configuration resource - weightType(EPINIO_TYPES.CONFIGURATION, 200, true); configureType(EPINIO_TYPES.CONFIGURATION, { isCreatable: true, isEditable: true, @@ -92,12 +100,11 @@ export function init($plugin: any, store: any) { customRoute: createEpinioRoute('c-cluster-resource', { resource: EPINIO_TYPES.CONFIGURATION }), }); + // Groups + const ADVANCED_GROUP = 'Advanced'; const SERVICE_GROUP = 'Services'; - weightGroup(SERVICE_GROUP, 1, true); - // Service Instance - weightType(EPINIO_TYPES.SERVICE_INSTANCE, 151, true); configureType(EPINIO_TYPES.SERVICE_INSTANCE, { isCreatable: true, isEditable: true, @@ -108,7 +115,6 @@ export function init($plugin: any, store: any) { }); // Catalog Service - weightType(EPINIO_TYPES.CATALOG_SERVICE, 150, true); configureType(EPINIO_TYPES.CATALOG_SERVICE, { isCreatable: false, isEditable: false, @@ -119,7 +125,6 @@ export function init($plugin: any, store: any) { }); // Namespace resource - weightType(EPINIO_TYPES.NAMESPACE, 100, true); configureType(EPINIO_TYPES.NAMESPACE, { isCreatable: true, isEditable: true, @@ -130,16 +135,30 @@ export function init($plugin: any, store: any) { showListMasthead: false // Disable default masthead because we provide a custom one. }); + // Side Nav + weightType(EPINIO_TYPES.CATALOG_SERVICE, 150, true); + weightType(EPINIO_TYPES.SERVICE_INSTANCE, 151, true); basicType([ EPINIO_TYPES.SERVICE_INSTANCE, EPINIO_TYPES.CATALOG_SERVICE, ], SERVICE_GROUP); + weightType(EPINIO_TYPES.CONFIGURATION, 200, true); + weightType(EPINIO_TYPES.APP_CHARTS, 150, true); + basicType([ + EPINIO_TYPES.CONFIGURATION, + EPINIO_TYPES.APP_CHARTS + ], ADVANCED_GROUP); + + weightType(EPINIO_TYPES.APP, 300, true); + weightGroup(SERVICE_GROUP, 2, true); + weightType(EPINIO_TYPES.NAMESPACE, 100, true); + weightGroup(ADVANCED_GROUP, 1, true); basicType([ EPINIO_TYPES.APP, SERVICE_GROUP, EPINIO_TYPES.NAMESPACE, - EPINIO_TYPES.CONFIGURATION + ADVANCED_GROUP ]); headers(EPINIO_TYPES.APP, [ @@ -150,8 +169,6 @@ export function init($plugin: any, store: any) { labelKey: 'epinio.tableHeaders.namespace', value: 'meta.namespace', sort: ['meta.namespace'], - formatter: 'LinkDetail', - formatterOpts: { reference: 'nsLocation' } }, { name: 'dep-status', @@ -266,8 +283,6 @@ export function init($plugin: any, store: any) { labelKey: 'epinio.tableHeaders.namespace', value: 'meta.namespace', sort: ['meta.namespace'], - formatter: 'LinkDetail', - formatterOpts: { reference: 'nsLocation' } }, { name: 'boundApps', @@ -298,20 +313,26 @@ export function init($plugin: any, store: any) { headers(EPINIO_TYPES.SERVICE_INSTANCE, [ STATE, - SIMPLE_NAME, + NAME, { name: 'namespace', labelKey: 'epinio.tableHeaders.namespace', value: 'metadata.namespace', sort: ['metadata.namespace'], - formatter: 'LinkDetail', - formatterOpts: { reference: 'nsLocation' } }, - { // This will be a link once the service instance detail / create / edit pages are created - name: 'catalog_service', - labelKey: 'epinio.serviceInstance.tableHeaders.service', - value: 'catalog_service', - sort: ['catalog_service'], + { + name: 'catalog_service', + labelKey: 'epinio.serviceInstance.tableHeaders.service', + value: 'catalog_service', + sort: ['catalog_service'], + formatter: 'LinkDetail', + formatterOpts: { reference: 'serviceLocation' } + }, + { + name: 'catalog_service_version', + labelKey: 'epinio.serviceInstance.tableHeaders.serviceVersion', + value: 'catalog_service_version', + sort: ['catalog_service_version'], }, { name: 'boundApps', @@ -338,4 +359,21 @@ export function init($plugin: any, store: any) { }, AGE ]); + + headers(EPINIO_TYPES.APP_CHARTS, [ + SIMPLE_NAME, + { + name: 'description', + labelKey: 'epinio.catalogService.tableHeaders.desc', + value: 'description', + sort: ['description'], + }, + { + name: 'helm_chart', + label: 'Helm Chart', + value: 'helm_chart', + sort: ['helm_chart'], + }, + AGE + ]); } diff --git a/pkg/epinio/detail/applications.vue b/pkg/epinio/detail/applications.vue index 267d7c5d9f..0ffd24bbf1 100644 --- a/pkg/epinio/detail/applications.vue +++ b/pkg/epinio/detail/applications.vue @@ -8,6 +8,8 @@ import ResourceTable from '@shell/components/ResourceTable.vue'; import PlusMinus from '@shell/components/form/PlusMinus.vue'; import { epinioExceptionToErrorsArray } from '../utils/errors'; import ApplicationCard from '@/shell/components/cards/ApplicationCard.vue'; +import Tabbed from '@shell/components/Tabbed/index.vue'; +import Tab from '@shell/components/Tabbed/Tab.vue'; interface Data { } @@ -19,7 +21,9 @@ export default Vue.extend({ ConsumptionGauge, ResourceTable, PlusMinus, - ApplicationCard + ApplicationCard, + Tabbed, + Tab, }, props: { value: { @@ -35,12 +39,35 @@ export default Vue.extend({ required: true }, }, + fetch() { + this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.SERVICE_INSTANCE }); + this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.CONFIGURATION }); + }, + data() { + const appInstanceSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.APP_INSTANCE); + const servicesSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.SERVICE_INSTANCE); + const servicesHeaders: [] = this.$store.getters['type-map/headersFor'](servicesSchema); + const configsSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.CONFIGURATION); + const configsHeaders: [] = this.$store.getters['type-map/headersFor'](configsSchema); + return { - appInstanceSchema: this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.APP_INSTANCE), saving: false, + appInstance: { + schema: appInstanceSchema, + headers: this.$store.getters['type-map/headersFor'](appInstanceSchema), + }, + services: { + schema: servicesSchema, + headers: servicesHeaders.filter((h: any) => !['namespace', 'boundApps'].includes(h.name)), + }, + configs: { + schema: configsSchema, + headers: configsHeaders.filter((h: any) => !['namespace', 'boundApps', 'service'].includes(h.name)), + } }; }, + methods: { async updateInstances(newInstances: number) { this.$set(this, 'saving', true); @@ -58,6 +85,12 @@ export default Vue.extend({ return `${ matchGithub?.[4] }/${ matchGithub?.[5] }`; } + }, + + computed: { + sourceIcon(): string { + return this.value.sourceInfo?.icon || 'icon-epinio'; + } } }); @@ -68,7 +101,7 @@ export default Vue.extend({ @@ -87,25 +120,24 @@ export default Vue.extend({ -

+

{{ t('epinio.applications.detail.deployment.label') }}

-
+
({ {{ t('tableHeaders.memory') }} - {{ value.instanceMemory.min }} MiB - {{ value.instanceMemory.max }} MiB - {{ value.instanceMemory.avg }} MiB + {{ value.instanceMemory.min }} + {{ value.instanceMemory.max }} + {{ value.instanceMemory.avg }} {{ t('tableHeaders.cpu') }} - {{ value.instanceCpu.min }} m - {{ value.instanceCpu.max }} m - {{ value.instanceCpu.avg }} m + {{ value.instanceCpu.min }} + {{ value.instanceCpu.max }} + {{ value.instanceCpu.avg }}
+ +

+ {{ t('epinio.applications.detail.tables.label') }} +

+
- - - - + + + + + + + + + + + + + +
@@ -292,7 +337,6 @@ export default Vue.extend({ } .deployment { - margin-bottom: 60px; .simple-box { width: 100%; margin-bottom: 0; diff --git a/pkg/epinio/detail/catalogservices.vue b/pkg/epinio/detail/catalogservices.vue new file mode 100644 index 0000000000..fa1e110798 --- /dev/null +++ b/pkg/epinio/detail/catalogservices.vue @@ -0,0 +1,49 @@ + + + diff --git a/pkg/epinio/edit/configurations.vue b/pkg/epinio/edit/configurations.vue index d60f4d0d7e..5cd249f0a3 100644 --- a/pkg/epinio/edit/configurations.vue +++ b/pkg/epinio/edit/configurations.vue @@ -48,7 +48,7 @@ export default Vue.extend({ async fetch() { await this.mixinFetch(); - Vue.set(this.value.meta, 'namespace', this.initialValue.meta.namespace || this.namespaces[0].metadata.name); + Vue.set(this.value.meta, 'namespace', this.initialValue.meta.namespace || this.namespaces[0]?.metadata.name); this.selectedApps = [...this.initialValue.configuration?.boundapps || []]; }, @@ -105,8 +105,9 @@ export default Vue.extend({ updateValidation() { const nameErrors = validateKubernetesName(this.value?.meta.name || '', this.t('epinio.namespace.name'), this.$store.getters, undefined, []); + const nsErrors = validateKubernetesName(this.value?.meta.namespace || '', '', this.$store.getters, undefined, []); - if (nameErrors.length === 0) { + if (nameErrors.length === 0 && nsErrors.length === 0) { const dataValues = Object.entries(this.value?.data || {}); if (!!dataValues.length) { @@ -124,6 +125,7 @@ export default Vue.extend({ watch: { 'value.meta.namespace'() { Vue.set(this, 'selectedApps', []); + this.updateValidation(); // For when a user is supplying their own ns }, 'value.meta.name'() { @@ -142,14 +144,9 @@ export default Vue.extend({