diff --git a/packages/noodl-editor/src/editor/src/contexts/PluginContext/commands/upload-aws-s3.ts b/packages/noodl-editor/src/editor/src/contexts/PluginContext/commands/upload-aws-s3.ts index 8ea95fe..84cdf55 100644 --- a/packages/noodl-editor/src/editor/src/contexts/PluginContext/commands/upload-aws-s3.ts +++ b/packages/noodl-editor/src/editor/src/contexts/PluginContext/commands/upload-aws-s3.ts @@ -48,7 +48,7 @@ export async function execute(command: Command, event: MessageEvent) { // Deploy to temp folder await compilation.deployToFolder(tempDir, { - environment: command.cloudService ? new Environment(command.cloudService) : undefined + environment: command.cloudService ? new Environment("", command.cloudService) : undefined }); // Upload to S3 diff --git a/packages/noodl-editor/src/editor/src/models/CloudServices/CloudService.ts b/packages/noodl-editor/src/editor/src/models/CloudServices/CloudService.ts index 0a22090..7437127 100644 --- a/packages/noodl-editor/src/editor/src/models/CloudServices/CloudService.ts +++ b/packages/noodl-editor/src/editor/src/models/CloudServices/CloudService.ts @@ -1,9 +1,9 @@ -import { Environment, ExternalCloudService } from '@noodl-models/CloudServices/ExternalCloudService'; import { CloudServiceEvent, CloudServiceEvents, CreateEnvironment, CreateEnvironmentRequest, + Environment, ICloudBackendService, ICloudService, UpdateEnvironmentRequest @@ -12,6 +12,9 @@ import { ProjectModel } from '@noodl-models/projectmodel'; import { getCloudServices } from '@noodl-models/projectmodel.editor'; import { Model } from '@noodl-utils/model'; +import { GlobalCloudService } from './providers/GlobalCloudService'; +import { ProjectCloudService } from './providers/ProjectCloudService'; + export type CloudQueueItem = { frontendId: string; environmentId: string; @@ -20,7 +23,9 @@ export type CloudQueueItem = { class CloudBackendService implements ICloudBackendService { private _isLoading = false; private _collection?: Environment[]; - private _localExternal = new ExternalCloudService(); + + private _globalProvider = new GlobalCloudService(); + private _projectProvider = new ProjectCloudService(); get isLoading(): boolean { return this._isLoading; @@ -32,12 +37,14 @@ class CloudBackendService implements ICloudBackendService { constructor(private readonly service: CloudService) {} - async fetch(): Promise { + async fetch(project: ProjectModel): Promise { this._isLoading = true; try { - // Fetch environments from local machine - const localEnvironments = await this._localExternal.list(); - this._collection = localEnvironments.map((x) => new Environment(x)); + const projectResults = await this._projectProvider.list(project); + this._collection = projectResults.map((x) => new Environment("project", x)); + + const globalResults = await this._globalProvider.list(project); + this._collection = this._collection.concat(globalResults.map((x) => new Environment("global", x))); } finally { this._isLoading = false; this.service.notifyListeners(CloudServiceEvent.BackendUpdated); @@ -48,7 +55,7 @@ class CloudBackendService implements ICloudBackendService { async fromProject(project: ProjectModel): Promise { const activeCloudServices = getCloudServices(project); if (!this._collection) { - await this.fetch(); + await this.fetch(project); } return this.items.find((b) => { @@ -56,16 +63,16 @@ class CloudBackendService implements ICloudBackendService { }); } - async create(options: CreateEnvironmentRequest): Promise { - return await this._localExternal.create(options); + async create(project: ProjectModel, options: CreateEnvironmentRequest): Promise { + return await this._projectProvider.create(project, options); } - async update(options: UpdateEnvironmentRequest): Promise { - return await this._localExternal.update(options); + async update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise { + return await this._projectProvider.update(project, options); } - async delete(id: string): Promise { - return await this._localExternal.delete(id); + async delete(project: ProjectModel, id: string): Promise { + return await this._projectProvider.delete(project, id); } } @@ -95,15 +102,11 @@ export class CloudService extends Model i */ public async prefetch() { this.reset(); - await this.fetch(); - } - - public async fetch() { - await this.backend.fetch(); + await this.backend.fetch(ProjectModel.instance); } public async getActiveEnvironment(project: ProjectModel): Promise { - await this.backend.fetch(); + await this.backend.fetch(project); return this.backend.fromProject(project); } } diff --git a/packages/noodl-editor/src/editor/src/models/CloudServices/index.ts b/packages/noodl-editor/src/editor/src/models/CloudServices/index.ts index 1b2e4e0..30078eb 100644 --- a/packages/noodl-editor/src/editor/src/models/CloudServices/index.ts +++ b/packages/noodl-editor/src/editor/src/models/CloudServices/index.ts @@ -1,3 +1,2 @@ export * from './CloudService'; export * from './type'; -export * from './ExternalCloudService'; diff --git a/packages/noodl-editor/src/editor/src/models/CloudServices/ExternalCloudService.ts b/packages/noodl-editor/src/editor/src/models/CloudServices/providers/GlobalCloudService.ts similarity index 63% rename from packages/noodl-editor/src/editor/src/models/CloudServices/ExternalCloudService.ts rename to packages/noodl-editor/src/editor/src/models/CloudServices/providers/GlobalCloudService.ts index 2c7f2f4..faf9b78 100644 --- a/packages/noodl-editor/src/editor/src/models/CloudServices/ExternalCloudService.ts +++ b/packages/noodl-editor/src/editor/src/models/CloudServices/providers/GlobalCloudService.ts @@ -1,25 +1,21 @@ import { JSONStorage } from '@noodl/platform'; -import { CreateEnvironment, CreateEnvironmentRequest, UpdateEnvironmentRequest } from '@noodl-models/CloudServices'; +import { + CreateEnvironment, + CreateEnvironmentRequest, + EnvironmentDataFormat, + ICloudServiceProvider, + UpdateEnvironmentRequest +} from '@noodl-models/CloudServices'; +import { ProjectModel } from '@noodl-models/projectmodel'; -/** The data format is separated from our internal model. */ -export type EnvironmentDataFormat = { - enabled: boolean; - id: string; - name: string; - description: string; - masterKey: string; - appId: string; - endpoint: string; -}; - -export class ExternalCloudService { - async list(): Promise { +export class GlobalCloudService implements ICloudServiceProvider { + async list(_project: ProjectModel): Promise { const local = await JSONStorage.get('externalBrokers'); return local.brokers || []; } - async create(options: CreateEnvironmentRequest): Promise { + async create(_project: ProjectModel, options: CreateEnvironmentRequest): Promise { const id = `${options.url}-${options.appId}`; const newBroker: EnvironmentDataFormat = { @@ -44,7 +40,7 @@ export class ExternalCloudService { }; } - async update(options: UpdateEnvironmentRequest): Promise { + async update(_project: ProjectModel, options: UpdateEnvironmentRequest): Promise { const local = await JSONStorage.get('externalBrokers'); const brokers: EnvironmentDataFormat[] = local.brokers || []; @@ -63,7 +59,7 @@ export class ExternalCloudService { return true; } - async delete(id: string): Promise { + async delete(_project: ProjectModel, id: string): Promise { const local = await JSONStorage.get('externalBrokers'); const brokers: EnvironmentDataFormat[] = local.brokers || []; @@ -79,25 +75,3 @@ export class ExternalCloudService { return true; } } - -export class Environment { - id: string; - name: string; - description: string; - createdAt: string; - masterKeyUpdatedAt: string; - masterKey: string; - appId: string; - url: string; - - constructor(item: EnvironmentDataFormat) { - this.id = item.id; - this.name = item.name; - this.description = item.description; - this.createdAt = ''; - this.masterKeyUpdatedAt = ''; - this.masterKey = item.masterKey; - this.appId = item.appId; - this.url = item.endpoint; - } -} diff --git a/packages/noodl-editor/src/editor/src/models/CloudServices/providers/ProjectCloudService.ts b/packages/noodl-editor/src/editor/src/models/CloudServices/providers/ProjectCloudService.ts new file mode 100644 index 0000000..cd24044 --- /dev/null +++ b/packages/noodl-editor/src/editor/src/models/CloudServices/providers/ProjectCloudService.ts @@ -0,0 +1,101 @@ +import { filesystem } from '@noodl/platform'; + +import { + CreateEnvironment, + CreateEnvironmentRequest, + EnvironmentDataFormat, + ICloudServiceProvider, + UpdateEnvironmentRequest +} from '@noodl-models/CloudServices'; +import { ProjectModel } from '@noodl-models/projectmodel'; + +/** + * Store the Cloud Service relative to the project folder. + */ +export class ProjectCloudService implements ICloudServiceProvider { + private async load(project: ProjectModel) { + const dirpath = filesystem.resolve(project._retainedProjectDirectory, ".noodl"); + const filepath = filesystem.resolve(dirpath, "cloudservices.json"); + if (!filesystem.exists(filepath)) { + return [] + } + + // TODO: Validate file content + return filesystem.readJson(filepath); + } + + private async save(project: ProjectModel, items: EnvironmentDataFormat[]) { + const dirpath = filesystem.resolve(project._retainedProjectDirectory, ".noodl"); + const filepath = filesystem.resolve(dirpath, "cloudservices.json"); + if (!filesystem.exists(filepath)) { + await filesystem.makeDirectory(dirpath); + } + + await filesystem.writeJson(filepath, items); + } + + async list(project: ProjectModel): Promise { + if (!project || !project._retainedProjectDirectory) { + return [] + } + + return await this.load(project); + } + + async create(project: ProjectModel, options: CreateEnvironmentRequest): Promise { + const id = `${options.url}-${options.appId}`; + + const newBroker: EnvironmentDataFormat = { + enabled: true, + id, + name: options.name, + description: options.description, + masterKey: options.masterKey, + appId: options.appId, + endpoint: options.url + }; + + const current = await this.load(project); + await this.save(project, [...current, newBroker]); + + return { + id: newBroker.id, + appId: newBroker.appId, + url: newBroker.endpoint, + masterKey: newBroker.masterKey + }; + } + + async update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise { + const current = await this.load(project); + + // Find and update + const broker = current.find((x) => x.id === options.id); + if (!broker) return false; + + if (typeof options.name !== undefined) broker.name = options.name; + if (typeof options.description !== undefined) broker.description = options.description; + if (typeof options.appId !== undefined) broker.appId = options.appId; + if (typeof options.masterKey !== undefined) broker.masterKey = options.masterKey; + if (typeof options.url !== undefined) broker.endpoint = options.url; + + await this.save(project, current); + + return true; + } + + async delete(project: ProjectModel, id: string): Promise { + const current = await this.load(project); + + // Find the environment + const found = current.find((b) => b.id === id); + if (found) { + // Delete the environment + current.splice(current.indexOf(found), 1); + } + + // Save the list + await this.save(project, current); + return true; + } +} diff --git a/packages/noodl-editor/src/editor/src/models/CloudServices/type.ts b/packages/noodl-editor/src/editor/src/models/CloudServices/type.ts index 9cc7d16..7a4ec75 100644 --- a/packages/noodl-editor/src/editor/src/models/CloudServices/type.ts +++ b/packages/noodl-editor/src/editor/src/models/CloudServices/type.ts @@ -1,4 +1,3 @@ -import { Environment } from '@noodl-models/CloudServices'; import { ProjectModel } from '@noodl-models/projectmodel'; import { IModel } from '@noodl-utils/model'; @@ -30,11 +29,11 @@ export interface ICloudBackendService { get isLoading(): boolean; get items(): Environment[]; - fetch(): Promise; + fetch(project: ProjectModel): Promise; fromProject(project: ProjectModel): Promise | undefined; - create(options: CreateEnvironmentRequest): Promise; - update(options: UpdateEnvironmentRequest): Promise; - delete(id: string): Promise; + create(project: ProjectModel, options: CreateEnvironmentRequest): Promise; + update(project: ProjectModel, options: UpdateEnvironmentRequest): Promise; + delete(project: ProjectModel, id: string): Promise; } export enum CloudServiceEvent { @@ -55,3 +54,54 @@ export interface ICloudService extends IModel; + create(project: ProjectModel | undefined, options: CreateEnvironmentRequest): Promise; + update(project: ProjectModel | undefined, options: UpdateEnvironmentRequest): Promise; + delete(project: ProjectModel | undefined, id: string): Promise; +} diff --git a/packages/noodl-editor/src/editor/src/utils/cloudformation.ts b/packages/noodl-editor/src/editor/src/utils/cloudformation.ts index f6471a7..25e091b 100644 --- a/packages/noodl-editor/src/editor/src/utils/cloudformation.ts +++ b/packages/noodl-editor/src/editor/src/utils/cloudformation.ts @@ -1,4 +1,5 @@ import { CloudService } from '@noodl-models/CloudServices'; +import { ProjectModel } from '@noodl-models/projectmodel'; import SchemaModel from '@noodl-models/schemamodel'; class FormCollection { @@ -193,7 +194,7 @@ export default class CloudFormation { }) { // Create new cloud services if needed if (options.cloudServices.id === undefined) { - CloudService.instance.backend.fetch().then((collection) => { + CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => { // TODO(OS): Cloud formation Cloud Service // // Make sure we have a unique name for the cloud services // const orgName = options.cloudServices.name; diff --git a/packages/noodl-editor/src/editor/src/utils/schemahandler.ts b/packages/noodl-editor/src/editor/src/utils/schemahandler.ts index c4cd696..d8160f2 100644 --- a/packages/noodl-editor/src/editor/src/utils/schemahandler.ts +++ b/packages/noodl-editor/src/editor/src/utils/schemahandler.ts @@ -41,7 +41,7 @@ export default class SchemaHandler { return; // No project broker } - CloudService.instance.backend.fetch().then((collection) => { + CloudService.instance.backend.fetch(ProjectModel.instance).then((collection) => { // Find by the Url / Endpoint and app id let environment = collection.find((b) => { return b.url === activeBroker.endpoint && b.appId === activeBroker.appId; diff --git a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCard/CloudServiceCard.tsx b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCard/CloudServiceCard.tsx index 2d162e6..f233649 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCard/CloudServiceCard.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCard/CloudServiceCard.tsx @@ -84,7 +84,7 @@ export function CloudServiceCard({
- {'Self hosted '} + {environment.typeDisplayName + ' '} {errorMessage && ({errorMessage})} {isEditorEnvironment && (Used in editor)}
diff --git a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCardItem/CloudServiceCardItem.tsx b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCardItem/CloudServiceCardItem.tsx index 16030f0..6c4c760 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCardItem/CloudServiceCardItem.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCardItem/CloudServiceCardItem.tsx @@ -5,6 +5,7 @@ import { CloudService, Environment } from '@noodl-models/CloudServices'; import { ToastType } from '../../../ToastLayer/components/ToastCard'; import { CloudServiceCard } from '../CloudServiceCard'; import { useCloudServiceContext } from '../CloudServicePanel.context'; +import { ProjectModel } from '@noodl-models/projectmodel'; export interface CloudServiceCardItemProps { environment: Environment; @@ -20,7 +21,7 @@ export function CloudServiceCardItem({ environment, deleteEnvironment }: CloudSe async function onDelete() { await deleteEnvironment(); await runActivity('Deleting cloud service...', async () => { - const response: boolean = await CloudService.instance.backend.delete(environment.id); + const response: boolean = await CloudService.instance.backend.delete(ProjectModel.instance, environment.id); return { type: ToastType.Success, message: 'Cloud service deleted' diff --git a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCreateModal/CloudServiceCreateModal.tsx b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCreateModal/CloudServiceCreateModal.tsx index 67123eb..6a9dbed 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCreateModal/CloudServiceCreateModal.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceCreateModal/CloudServiceCreateModal.tsx @@ -12,6 +12,7 @@ import { Text, TextType } from '@noodl-core-ui/components/typography/Text'; import { ToastType } from '../../../ToastLayer/components/ToastCard'; import { useCloudServiceContext } from '../CloudServicePanel.context'; +import { ProjectModel } from '@noodl-models/projectmodel'; function isValidParseUrl(url: string) { if (!url) return false; @@ -63,7 +64,7 @@ export function CloudServiceCreateModal({ isVisible, onClose }: CloudServiceCrea async function onCreate() { await runActivity('Creating Cloud Service...', async () => { - await cloudService.backend.create({ + await cloudService.backend.create(ProjectModel.instance, { name, description, masterKey: masterKey ? masterKey : undefined, diff --git a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceModal/CloudSerivceModal.tsx b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceModal/CloudSerivceModal.tsx index aae8e53..24b1d01 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceModal/CloudSerivceModal.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServiceModal/CloudSerivceModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { FeedbackType } from '@noodl-constants/FeedbackType'; import { CloudService, Environment } from '@noodl-models/CloudServices'; @@ -12,6 +12,7 @@ import { HStack, VStack } from '@noodl-core-ui/components/layout/Stack'; import { Text } from '@noodl-core-ui/components/typography/Text'; import { ToastLayer } from '../../../ToastLayer'; +import { ProjectModel } from '@noodl-models/projectmodel'; export interface CloudServiceModalProps { isVisible: boolean; @@ -60,7 +61,7 @@ function AsSelfHosted({ } CloudService.instance.backend - .update({ + .update(ProjectModel.instance, { id: environment.id, name, description, @@ -70,7 +71,7 @@ function AsSelfHosted({ }) .then(() => { ToastLayer.showSuccess(`Updated Cloud Service`); - CloudService.instance.backend.fetch(); + CloudService.instance.backend.fetch(ProjectModel.instance); }) .catch(() => { ToastLayer.showError(`Failed to update Cloud Service`); diff --git a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServicePanel.context.tsx b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServicePanel.context.tsx index 8fa7518..e0e4be0 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServicePanel.context.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/CloudServicePanel/CloudServicePanel.context.tsx @@ -35,7 +35,7 @@ export function CloudServiceContextProvider({ children }) { const { hasActivity, runActivity } = useActivityQueue({ onSuccess: async () => { // Always fetch all the backends after something have changed - await cloudService.backend.fetch(); + await cloudService.backend.fetch(ProjectModel.instance); } });