diff --git a/dashboard/pkg/epinio/components/application/AppConfiguration.vue b/dashboard/pkg/epinio/components/application/AppConfiguration.vue new file mode 100644 index 00000000..e2f4b3b6 --- /dev/null +++ b/dashboard/pkg/epinio/components/application/AppConfiguration.vue @@ -0,0 +1,180 @@ + + + + + \ No newline at end of file diff --git a/dashboard/pkg/epinio/components/application/AppGitDeployment.vue b/dashboard/pkg/epinio/components/application/AppGitDeployment.vue new file mode 100644 index 00000000..e69de29b diff --git a/dashboard/pkg/epinio/components/application/AppInfo.vue b/dashboard/pkg/epinio/components/application/AppInfo.vue new file mode 100644 index 00000000..afd7c29b --- /dev/null +++ b/dashboard/pkg/epinio/components/application/AppInfo.vue @@ -0,0 +1,228 @@ + + + diff --git a/dashboard/pkg/epinio/components/application/AppProgress.vue b/dashboard/pkg/epinio/components/application/AppProgress.vue new file mode 100644 index 00000000..3ce0e6b1 --- /dev/null +++ b/dashboard/pkg/epinio/components/application/AppProgress.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/dashboard/pkg/epinio/components/application/AppSource.vue b/dashboard/pkg/epinio/components/application/AppSource.vue new file mode 100644 index 00000000..728c244c --- /dev/null +++ b/dashboard/pkg/epinio/components/application/AppSource.vue @@ -0,0 +1,522 @@ + + + + + + \ No newline at end of file diff --git a/dashboard/pkg/epinio/components/settings/ChartValues.vue b/dashboard/pkg/epinio/components/settings/ChartValues.vue new file mode 100644 index 00000000..bde15b1b --- /dev/null +++ b/dashboard/pkg/epinio/components/settings/ChartValues.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/dashboard/pkg/epinio/models/application-action.js b/dashboard/pkg/epinio/models/application-action.js new file mode 100644 index 00000000..427480e3 --- /dev/null +++ b/dashboard/pkg/epinio/models/application-action.js @@ -0,0 +1,172 @@ +import { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, APPLICATION_SOURCE_TYPE, EPINIO_PRODUCT_NAME } from '../types'; +import { epinioExceptionToErrorsArray } from '../utils/errors'; +import Resource from '@shell/plugins/dashboard-store/resource-class'; +import { set } from '@shell/utils/object'; // Vue 3-compatible reactivity setter + +export const APPLICATION_ACTION_TYPE = { + CREATE_NS: 'create_namespace', + CREATE: 'create', + UPDATE_SOURCE: 'updateSource', + GIT_FETCH: 'gitFetch', + UPLOAD: 'upload', + BIND_CONFIGURATIONS: 'bind_configurations', + BIND_SERVICES: 'bind_services', + BUILD: 'build', + DEPLOY: 'deploy', +}; + +export default class ApplicationActionResource extends Resource { + run = true; + state = APPLICATION_ACTION_STATE.PENDING; + + get name() { + return this.t(`epinio.applications.action.${ this.action }.label`); + } + + get description() { + return this.t(`epinio.applications.action.${ this.action }.description`); + } + + get stateObj() { + switch (this.state) { + case APPLICATION_ACTION_STATE.SUCCESS: + return { name: 'succeeded', error: false, transitioning: false }; + case APPLICATION_ACTION_STATE.RUNNING: + return { name: 'pending', error: false, transitioning: true }; + case APPLICATION_ACTION_STATE.FAIL: + return { + name: 'fail', error: true, transitioning: false, message: this.stateMessage + }; + case APPLICATION_ACTION_STATE.PENDING: + default: + return { name: 'pending', error: false, transitioning: false }; + } + } + + async execute(params) { + try { + set(this, 'state', APPLICATION_ACTION_STATE.RUNNING); + + await this.innerExecute(params); + + set(this, 'state', APPLICATION_ACTION_STATE.SUCCESS); + set(this, 'run', false); + } catch (err) { + set(this, 'state', APPLICATION_ACTION_STATE.FAIL); + set(this, 'stateMessage', epinioExceptionToErrorsArray(err)[0].toString()); + throw err; + } + } + + async innerExecute(params) { + switch (this.action) { + case APPLICATION_ACTION_TYPE.CREATE_NS: return this.createNamespace(params); + case APPLICATION_ACTION_TYPE.CREATE: return this.create(params); + case APPLICATION_ACTION_TYPE.UPDATE_SOURCE: return this.updateSource(); + case APPLICATION_ACTION_TYPE.BIND_CONFIGURATIONS: return this.bindConfigurations(params); + case APPLICATION_ACTION_TYPE.BIND_SERVICES: return this.bindServices(params); + case APPLICATION_ACTION_TYPE.GIT_FETCH: return this.gitFetch(params); + case APPLICATION_ACTION_TYPE.UPLOAD: return this.upload(params); + case APPLICATION_ACTION_TYPE.BUILD: return this.build(params); + case APPLICATION_ACTION_TYPE.DEPLOY: return this.deploy(params); + } + } + + async createNamespace() { + const ns = await this.$dispatch(`${ EPINIO_PRODUCT_NAME }/createNamespace`, { + name: this.application.meta.namespace + }, { root: true }); + + await ns.create(); + } + + async create() { + await this.application.create(); + } + + async bindConfigurations() { + await this.application.updateConfigurations([], this.bindings.configurations); + } + + async bindServices() { + await this.application.updateServices([], this.bindings.services); + } + + async upload({ source }) { + await this.application.storeArchive(source.archive.tarball); + } + + async gitFetch({ source }) { + const rev = source.git?.commit || source.gitUrl?.branch; + const url = source.git?.url || source.gitUrl?.url; + + return await this.application.gitFetch(url, rev); + } + + async build({ source }) { + const { stage } = await this.application.stage( + this.application.buildCache.store.blobUid, + source.builderImage.value + ); + + this.application.showStagingLog(stage.id); + await this.application.waitForStaging(stage.id); + } + + async updateSource() { + await this.application.update({ restart: false }); + } + + async deploy({ source }) { + const stageId = source.type === APPLICATION_SOURCE_TYPE.ARCHIVE + ? this.application.buildCache.stage.stage.id + : null; + + const image = source.type === APPLICATION_SOURCE_TYPE.CONTAINER_URL + ? source.container.url + : this.application.buildCache.stage.image; + + await this.application.deploy(stageId, image, this.createDeployOrigin(source)); + this.application.showAppLog(); + } + + createDeployOrigin(source) { + switch (source.type) { + case APPLICATION_SOURCE_TYPE.ARCHIVE: + return { + kind: APPLICATION_MANIFEST_SOURCE_TYPE.PATH, + archive: true, + path: source.archive.fileName, + }; + case APPLICATION_SOURCE_TYPE.FOLDER: + return { + kind: APPLICATION_MANIFEST_SOURCE_TYPE.PATH, + path: source.archive.fileName, + }; + case APPLICATION_SOURCE_TYPE.CONTAINER_URL: + return { + kind: APPLICATION_MANIFEST_SOURCE_TYPE.CONTAINER, + container: source.container.url, + }; + case APPLICATION_SOURCE_TYPE.GIT_URL: + return { + kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT, + git: { + revision: source.gitUrl.branch, + repository: source.gitUrl.url, + }, + }; + case APPLICATION_SOURCE_TYPE.GIT_HUB: + case APPLICATION_SOURCE_TYPE.GIT_LAB: + return { + kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT, + git: { + revision: source.git.commit, + repository: source.git.url, + branch: source.git.branch?.name, + provider: source.type, + }, + }; + } + } +} diff --git a/dashboard/pkg/epinio/models/namespaces.js b/dashboard/pkg/epinio/models/namespaces.js new file mode 100644 index 00000000..66f56ee3 --- /dev/null +++ b/dashboard/pkg/epinio/models/namespaces.js @@ -0,0 +1,85 @@ +import { EPINIO_TYPES } from '../types'; +import EpinioMetaResource from './epinio-namespaced-resource'; + +export default class EpinioNamespace extends EpinioMetaResource { + get links() { + return { + self: this.getUrl(), + remove: this.getUrl(), + create: this.getUrl(null), + update: this.getUrl(), + }; + } + + async create() { + await this.followLink('create', { + method: 'post', + headers: { + 'content-type': 'application/json', + accept: 'application/json' + }, + data: { name: this.meta.name } + }); + + const namespaces = await this.$dispatch('findAll', { type: this.type, opt: { force: true } }); + + // Find new namespace + return namespaces.filter((n) => n.name === this.name)?.[0]; + } + + save() { + return this.create(); + } + + get canClone() { + return false; + } + + get canViewInApi() { + return false; + } + + get canCustomEdit() { + return false; + } + + get appCount() { + return this.apps?.length || 0; + } + + get configCount() { + return this.configurations?.length || 0; + } + + // ------------------------------------------------------------------ + + getUrl(name = this.meta.name) { + // Add baseUrl in a generic way + return this.$getters['urlFor'](this.type, this.id, { url: `/api/v1/namespaces/${ name || '' }` }); + } + + // ------------------------------------------------------------------ + + confirmRemove() { + return true; + } + + async remove() { + const allTabs = this.$rootGetters['wm/allTabs']; + + if (this.apps?.length && allTabs?.length) { + this.apps.forEach((e) => { + const app = this.$getters['byId'](EPINIO_TYPES.APP, `${ this.name }/${ e }`); + + if (!!app && app.meta.namespace === this.name) { + app.closeWindows(); + } + }); + } + await super.remove(); + } + + get warnDeletionMessage() { + return this.t('epinio.namespace.deleteWarning'); + } +} diff --git a/dashboard/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue b/dashboard/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue new file mode 100644 index 00000000..df744b1f --- /dev/null +++ b/dashboard/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/dashboard/pkg/epinio/pages/c/_cluster/applications/index.vue b/dashboard/pkg/epinio/pages/c/_cluster/applications/index.vue new file mode 100644 index 00000000..61ce72e7 --- /dev/null +++ b/dashboard/pkg/epinio/pages/c/_cluster/applications/index.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/dashboard/pkg/epinio/routing/epinio-routing.ts b/dashboard/pkg/epinio/routing/epinio-routing.ts index 0d8af902..d97dc1bf 100644 --- a/dashboard/pkg/epinio/routing/epinio-routing.ts +++ b/dashboard/pkg/epinio/routing/epinio-routing.ts @@ -1,6 +1,8 @@ // Don't forget to create a VueJS page called index.vue in the /pages folder!!! import ListEpinio from "../pages/index.vue"; import Dashboard from "../pages/c/_cluster/dashboard.vue"; +import ListApp from "../pages/c/_cluster/applications/index.vue"; +import CreateApp from "../pages/c/_cluster/applications/createapp/index.vue"; import AboutEpinio from "../pages/c/_cluster/about.vue"; // import { BLANK_CLUSTER } from '../config/epinio'; import { EPINIO_PRODUCT_NAME } from '../types'; @@ -42,13 +44,13 @@ const routes = [ { name: `${EPINIO_PRODUCT_NAME}-c-cluster-applications-createapp`, path: `/:product/c/:cluster/applications/createapp`, - component: ListEpinio, //CreateApp, + component: CreateApp, meta, }, { name: `${EPINIO_PRODUCT_NAME}-c-cluster-applications`, path: `/:product/c/:cluster/applications`, - component: ListEpinio, //ListApp + component: ListApp, meta: { product: EPINIO_PRODUCT_NAME, pkg: EPINIO_PRODUCT_NAME, diff --git a/dashboard/pkg/epinio/types.ts b/dashboard/pkg/epinio/types.ts index 106df287..ca623b8c 100644 --- a/dashboard/pkg/epinio/types.ts +++ b/dashboard/pkg/epinio/types.ts @@ -239,3 +239,26 @@ export interface EpinioNamespace extends EpinioMetaProperty { } export type EpinioCompRecord = Record + +export interface EpinioAppInfo { + meta: { + name: string, + namespace: string + }, + chart?: {}, + configuration: { + configurations: string[], + instances: number, + environment: { [key: string] : any } + settings: { [key: string] : any } + routes: string[] + } +} + +export const EPINIO_APP_MANIFEST = 'manifest'; + +export interface EpinioAppBindings { + configurations: string[], + services: EpinioService[], +} + diff --git a/dashboard/pkg/epinio/utils/settings.ts b/dashboard/pkg/epinio/utils/settings.ts new file mode 100644 index 00000000..73d01cc5 --- /dev/null +++ b/dashboard/pkg/epinio/utils/settings.ts @@ -0,0 +1,11 @@ +export function objValuesToString(obj: any) { + const copy = { ...obj }; + + for (const key in copy) { + if (typeof copy[key] !== 'string') { + copy[key] = String(copy[key]); + } + } + + return copy; +}