Beispiel #1
0
var BaseCluster = createClass({
	mixins: [State],

	__changeListeners: [],

	registerWithDispatcher: function (dispatcher) {
		this.dispatcherIndex = dispatcher.register(this.handleEvent.bind(this));
	},

	willInitialize: function (attrs) {
		this.registerWithDispatcher(Dispatcher);
		this.type = this.constructor.type;
		this.attrs = {};
		this.__parseAttributes(attrs);
		this.state = this.getInitialState();
		this.setState(this.__computeState(extend({
			credentials: attrs.credentials
		}, this.attrs)));
	},

	getInitialState: function () {
		return {
			selectedCloud: this.constructor.type,
			selectedRegionSlug: null,
			logEvents: [],
			credentialID: null,
			certVerified: false,
			domainName: null,
			caCert: null,
			dashboardLoginToken: null,
			deleting: false,
			failed: false,
			regions: [],
			numInstances: 1,
			credentials: [],
			__allCredentials: []
		};
	},

	__computeState: function (attrs) {
		var prevState = this.state;
		var state = {
			inProgress: false,
			deleting: prevState.deleting,
			failed: prevState.failed,
			logEvents: prevState.logEvents,
			steps: [
				{ id: 'configure', label: 'Configure', complete: false },
				{ id: 'install',   label: 'Install',   complete: false },
				{ id: 'dashboard', label: 'Dashboard', complete: false },
				{ id: 'done', visible: false }
			],
			selectedCloud: attrs.selectedCloud || attrs.type || prevState.selectedCloud,
			credentialID: attrs.credentialID || prevState.credentialID,
			certVerified: attrs.certVerified || prevState.certVerified,

			domainName: attrs.domain ? attrs.domain.domain : prevState.domainName,
			caCert: attrs.ca_cert ? window.btoa(attrs.ca_cert) : prevState.caCert,
			dashboardLoginToken: attrs.dashboard_login_token || prevState.dashboardLoginToken,

			regions: attrs.regions || prevState.regions,
			numInstances: attrs.num_instances || prevState.numInstances,

			selectedRegionSlug: attrs.selectedRegionSlug || prevState.selectedRegionSlug,
			selectedRegion: null,

			errorMessage: attrs.hasOwnProperty('errorMessage') ? attrs.errorMessage : prevState.errorMessage,

			__allCredentials: attrs.credentials || prevState.__allCredentials
		};

		var credentialIDExists = false;
		state.credentials = state.__allCredentials.filter(function (creds) {
			var keep = creds.type === state.selectedCloud;
			if (keep && creds.id === state.credentialID) {
				credentialIDExists = true;
			}
			return keep;
		});

		if ( !credentialIDExists ) {
			state.credentialID = null;
		}

		if (state.selectedRegionSlug === null) {
			state.selectedRegionSlug = this.constructor.defaultRegionSlug;
		}
		if (state.selectedRegionSlug === null && state.regions.length > 0) {
			state.selectedRegionSlug = state.regions[0].slug;
		}
		for (var i = 0, len = state.regions.length; i < len; i++) {
			if (state.regions[i].slug === state.selectedRegionSlug) {
				state.selectedRegion = state.regions[i];
				break;
			}
		}

		state.currentStep = 'configure';
		var clusterState = attrs.state || this.attrs.state;
		switch (clusterState) {
		case 'starting':
			state.currentStep = 'install';
			state.inProgress = true;
			break;

		case 'deleting':
			state.currentStep = 'install';
			state.deleting = true;
			break;

		case 'error':
			state.failed = true;
			if (this.attrs.ID !== 'new') {
				state.currentStep = 'install';
			}
			break;

		case 'running':
			if (state.certVerified) {
				state.currentStep = 'done';
			} else {
				state.currentStep = 'dashboard';
			}
			break;
		}

		var complete = true;
		state.steps.forEach(function (step) {
			if (step.id === state.currentStep) {
				complete = false;
			}
			step.complete = complete;
		});

		return state;
	},

	__parseAttributes: function (data) {
		var jsonFields = this.constructor.jsonFields;
		var attrs = {};
		for (var k in jsonFields) {
			if (data.hasOwnProperty(k)) {
				attrs[jsonFields[k]] = data[k];
			}
		}
		attrs.name = attrs.ID;
		this.attrs = attrs;
	},

	setCredentials: function (credentials) {
		this.setState(this.__computeState({
			state: this.attrs.state,
			credentials: credentials
		}));
	},

	setAttrs: function (attrs) {
		this.__parseAttributes(attrs);
		this.setState(this.__computeState(this.attrs));
	},

	toJSON: function () {
		var data = {};
		var attrs = this.attrs;
		var jsonFields = this.constructor.jsonFields;
		for (var k in jsonFields) {
			if (attrs.hasOwnProperty(jsonFields[k])) {
				data[k] = attrs[jsonFields[k]];
			}
		}
		data.type = this.constructor.type;
		return data;
	},

	handleEvent: function (event) {
		if (event.name === 'LAUNCH_CLUSTER' && this.attrs.ID === 'new') {
			this.setState(this.__computeState({
				errorMessage: null
			}));
		}

		if (event.clusterID !== this.attrs.ID) {
			return;
		}

		var state;

		switch (event.name) {
		case 'LAUNCH_CLUSTER_FAILURE':
			this.setState(this.__computeState({
				state: 'error',
				errorMessage: event.res.message || ('Something went wrong ('+ event.xhr.status +')')
			}));
			break;

		case 'LOG':
			this.__addLog(event.data);
			break;

		case 'INSTALL_ERROR':
			this.__addLog({
				description: 'Error: '+ event.message
			});
			break;

		case 'CLUSTER_STATE':
			this.setState(
				this.__computeState({state: event.state})
			);
			break;

		case 'INSTALL_DONE':
			this.__parseAttributes(event.cluster);
			this.setState(this.__computeState(event.cluster));
			break;

		case 'CERT_VERIFIED':
			this.setState(this.__computeState({
				certVerified: true
			}));
			break;

		case 'SELECT_CREDENTIAL':
			this.setState(this.__computeState({
				credentialID: event.credentialID
			}));
			break;

		case 'CREDENTIALS_CHANGE':
			this.setState(this.__computeState({
				credentials: event.credentials
			}));
			break;

		case 'CLOUD_REGIONS':
			state = this.state;
			if (state.credentialID !== event.credentialID || state.selectedCloud !== event.cloud) {
				return;
			}
			this.setState(this.__computeState({
				regions: event.regions.sort(function (a, b) {
					return a.name.localeCompare(b.name);
				})
			}));
			break;

		case 'SELECT_REGION':
			this.setState(this.__computeState({
				selectedRegionSlug: event.region
			}));
			break;

		case 'SELECT_SIZE':
			this.setState(this.__computeState({
				selectedSizeSlug: event.slug
			}));
			break;

		case 'SELECT_NUM_INSTANCES':
			this.setState(this.__computeState({
				num_instances: event.numInstances
			}));
			break;
		}
	},

	__addLog: function (data) {
		var logEvents = [].concat(this.state.logEvents);
		logEvents.push(data);
		this.setState({
			logEvents: logEvents
		}, true);
	}
});
Beispiel #2
0
export default createClass({
	mixins: [State],

	registerWithDispatcher: function (dispatcher) {
		this.dispatcherIndex = dispatcher.register(this.handleEvent.bind(this));
	},

	willInitialize: function () {
		this.registerWithDispatcher(Dispatcher);

		this.__handleClusterChanged = this.__handleClusterChanged.bind(this);
		this.state = this.getInitialState();
		newCluster.addChangeListener(this.__handleClusterChanged);
		this.__changeListeners = [];
	},

	getInitialState: function () {
		return {
			clusters: [],
			credentials: [],
			currentClusterID: null,
			currentCloudSlug: 'aws',
			currentCluster: newCluster,
			errors: {
				credentials: []
			},
			prompts: {},
			credentialsPromptValue: {},
			progressMeters: {}
		};
	},

	handleEvent: function (event) {
		var cluster;
		switch (event.name) {
		case 'SELECT_CLOUD':
			if (newCluster.constructor.type !== event.cloud) {
				this.__setNewCluster(event.cloud);
				this.setState({
					currentCloudSlug: event.cloud,
					currentCluster: newCluster
				});
			}
			break;

		case 'LAUNCH_CLUSTER':
			Client.launchCluster(newCluster.toJSON(), newCluster.getBackupFile());
			break;

		case 'LAUNCH_CLUSTER_SUCCESS':
			this.__setNewCluster(newCluster.type);
			break;

		case 'NEW_CLUSTER':
			this.__addCluster(event.cluster);
			if (this.__pendingCurrentClusterID === event.cluster.attrs.ID) {
				this.__pendingCurrentClusterID = null;
				this.setState({
					currentClusterID: event.cluster.attrs.ID,
					currentCluster: event.cluster,
					currentCloudSlug: event.cluster.type
				});
			}
			break;

		case 'CLUSTER_UPDATE':
			this.__replaceCluster(event.cluster);
			break;

		case 'NEW_CREDENTIAL':
			this.__addCredential(event.credential);
			break;

		case 'CREDENTIAL_DELETED':
			this.__removeCredential(event.id);
			break;

		case 'PROMPT_SELECT_CREDENTIAL':
			this.setState({
				credentialsPromptValue: (function (credentialsPromptValue) {
					var item = {};
					item[event.clusterID] = event.credentialID;
					return extend({}, credentialsPromptValue, item);
				})(this.state.credentialsPromptValue)
			});
			break;

		case 'CURRENT_CLUSTER':
			cluster = event.clusterID === null ? newCluster : this.__findCluster(event.clusterID);
			if ( !cluster ) {
				this.__pendingCurrentClusterID = event.clusterID;
				break;
			}
			this.setState({
				currentClusterID: event.clusterID,
				currentCluster: cluster,
				currentCloudSlug: cluster.type
			});
			break;

		case 'CREATE_CREDENTIAL':
			this.__clearErrors('credentials');
			if (this.__findCredentialIndex(event.data.id) !== -1) {
				return;
			}
			this.__addCredential(event.data);
			this.__createCredPromise = Client.createCredential(event.data).catch(function (args) {
				var xhr = args[1];
				if (xhr.status === 409) {
					return; // Already exists
				}
				this.__removeCredential(event.data.id);
			}.bind(this));
			break;

		case 'DELETE_CREDENTIAL':
			this.__clearErrors('credentials');
			this.__removeCredential(event.creds.id);
			Client.deleteCredential(event.creds.type, event.creds.id).catch(function (args) {
				var xhr = args[1];
				if (xhr.status === 409) {
					// in use
					this.__addCredential(event.creds);
				}
			}.bind(this));
			break;

		case 'DELETE_CREDENTIAL_ERROR':
			this.__addError('credentials', event.err);
			break;

		case 'CREATE_CREDENTIAL_ERROR':
			this.__addError('credentials', event.err);
			break;

		case 'CONFIRM_CLUSTER_DELETE':
			Client.deleteCluster(event.clusterID);
			break;

		case 'INSTALL_PROMPT_REQUESTED':
			this.setState({
				prompts: (function () {
					var prompts = extend({}, this.state.prompts);
					prompts[event.clusterID] = event.prompt;
					return prompts;
				}.bind(this))()
			});
			break;

		case 'INSTALL_PROMPT_RESOLVED':
			this.setState({
				prompts: (function () {
					var prompts = extend({}, this.state.prompts);
					if ((prompts[event.clusterID] || {}).id === event.prompt.id) {
						delete prompts[event.clusterID];
					}
					return prompts;
				}.bind(this))()
			});
			break;

		case 'INSTALL_PROMPT_RESPONSE':
			Client.sendPromptResponse(event.clusterID, event.promptID, event.data);
			break;

		case 'PROGRESS':
			this.setState({
				progressMeters: (function (progressMeters) {
					var clusterID = event.clusterID;
					progressMeters[clusterID] = progressMeters[clusterID] || {};
					progressMeters[clusterID][event.data.id] = event.data;
					return progressMeters;
				})(extend({}, this.state.progressMeters))
			});
			break;

		case 'CHECK_CERT':
			cluster = this.__findCluster(event.clusterID);
			if (cluster) {
				Client.checkCert(event.domainName).then(function () {
					cluster.handleEvent({
						name: 'CERT_VERIFIED',
						clusterID: cluster.attrs.ID
					});
				}.bind(this));
			}
			break;

		case 'CREDENTIALS_CHANGE':
			newCluster.handleEvent(extend({}, event, {clusterID: 'new'}));
			this.state.clusters.forEach(function (c) {
				c.setCredentials(event.credentials);
			});
			break;

		case 'LIST_CLOUD_REGIONS':
			(this.__createCredPromise || Promise.resolve(null)).then(function () {
				Client.listCloudRegions(event.cloud, event.credentialID);
			});
			break;

		case 'LIST_AZURE_SUBSCRIPTIONS':
			(this.__createCredPromise || Promise.resolve(null)).then(function () {
				Client.listAzureSubscriptions(event.credentialID);
			});
			break;

		case 'CLOUD_REGIONS':
			newCluster.handleEvent(extend({}, event, {clusterID: 'new'}));
			break;

		case 'AZURE_SUBSCRIPTIONS':
			newCluster.handleEvent(extend({}, event, {clusterID: 'new'}));
			break;

		case 'CLUSTER_STATE':
			if (event.state === 'deleted') {
				this.__removeCluster(event.clusterID);
			}
			if (event.state !== 'starting') {
				this.setState({
					progressMeters: (function (progressMeters) {
						var clusterID = event.clusterID;
						if (progressMeters.hasOwnProperty(clusterID)) {
							delete progressMeters[clusterID];
						}
						return progressMeters;
					})(extend({}, this.state.progressMeters))
				});
			}
			break;
		}
	},

	__setNewCluster: function (t) {
		newCluster.removeChangeListener(this.__handleClusterChanged);
		newCluster = Cluster.newOfType(t, {id: 'new', credentials: this.state.credentials});
		newCluster.addChangeListener(this.__handleClusterChanged);
	},

	__addError: function (type, err) {
		var errors = this.state.errors;
		errors[type] = [err].concat(errors[type]);
		this.setState({
			errors: errors
		});
	},

	__clearErrors: function (type) {
		var errors = this.state.errors;
		errors[type] = [];
		this.setState({
			errors: errors
		});
	},

	__addCredential: function (data) {
		var index = this.__findCredentialIndex(data.id);
		if (index !== -1) {
			return;
		}
		var creds = [data].concat(this.state.credentials);
		this.setState({
			credentials: creds
		});
		this.__propagateCredentialsChange();
	},

	__removeCredential: function (credentialID) {
		var creds = this.state.credentials;
		var index = this.__findCredentialIndex(credentialID);
		if (index === -1) {
			return;
		}
		creds = creds.slice(0, index).concat(creds.slice(index+1));
		this.setState({
			credentials: creds
		});
		this.__propagateCredentialsChange();
	},

	__propagateCredentialsChange: function () {
		Dispatcher.dispatch({
			name: 'CREDENTIALS_CHANGE',
			credentials: this.state.credentials
		});
	},

	__findCredentialIndex: function (credentialID) {
		var creds = this.state.credentials;
		for (var i = 0, len = creds.length; i < len; i++) {
			if (creds[i].id === credentialID) {
				return i;
			}
		}
		return -1;
	},

	__addCluster: function (cluster) {
		var index = this.__findClusterIndex(cluster.attrs.ID);
		if (index !== -1) {
			window.console.warn('cluster '+ cluster.attrs.ID +' already added!');
			return;
		}
		var clusters = [cluster].concat(this.state.clusters);
		var newState = {
			clusters: clusters
		};
		cluster.setCredentials(this.state.credentials);
		if (cluster.attrs.ID === this.state.currentClusterID) {
			newState.currentCluster = cluster;
		}
		this.setState(newState);
		cluster.addChangeListener(this.__handleClusterChanged);
	},

	__replaceCluster: function (attrs) {
		var cluster = this.__findCluster(attrs.id);
		if (cluster === null) {
			window.console.warn('cluster '+ cluster.id +' not found!');
			return;
		}
		cluster.setAttrs(attrs);
	},

	__findClusterIndex: function (clusterID) {
		var clusters = this.state.clusters;
		for (var i = 0, len = clusters.length; i < len; i++) {
			if (clusters[i].attrs.ID === clusterID) {
				return i;
			}
		}
		return -1;
	},

	__findCluster: function (clusterID) {
		var index = this.__findClusterIndex(clusterID);
		if (index === -1) {
			return null;
		} else {
			return this.state.clusters[index];
		}
	},

	__removeCluster: function (clusterID) {
		var index = this.__findClusterIndex(clusterID);
		if (index === -1) {
			return;
		}
		var clusters = this.state.clusters;
		var cluster = clusters[index];
		clusters = clusters.slice(0, index).concat(clusters.slice(index+1));
		cluster.removeChangeListener(this.__handleClusterChanged);
		this.setState({
			clusters: clusters
		});
	},

	__handleClusterChanged: function () {
		// TODO: handle rapid fire change in chunks
		this.setState({
			clusters: this.state.clusters
		});
	}
});
Beispiel #3
0
var Client = createClass({
	displayName: "Client",

	mixins: [{
		ctor: {
			middleware: [
				SerializeJSONMiddleware,
				WithCredentialsMiddleware
			]
		}
	}],

	willInitialize: function (endpoints) {
		this.endpoints = endpoints;
		this.__cachedResponses = {};

		this.__waitForEventFns = [];
		Dispatcher.register(this.__handleEvent.bind(this));
	},

	performRequest: function (method, args) {
		if ( !args.url ) {
			var err = new Error(this.constructor.displayName +".prototype.performRequest(): Can't make request without URL");
			setTimeout(function () {
				throw err;
			}.bind(this), 0);
			return Promise.reject(err);
		}

		var cacheResponse = args.cacheResponse;
		var cachedResponseKey = method + args.url;
		var cachedRepsonses = this.__cachedResponses;
		if (cacheResponse && cachedRepsonses[cachedResponseKey]) {
			return Promise.resolve(cachedRepsonses[cachedResponseKey]);
		}

		var middleware = args.middleware || [];
		delete args.middleware;

		return HTTP(extend({
			method: method,
			middleware: [].concat(this.constructor.middleware).concat(middleware)
		}, args)).then(function (args) {
			var res = args[0];
			var xhr = args[1];
			return new Promise(function (resolve, reject) {
				if (xhr.status >= 200 && xhr.status < 400) {
					if (cacheResponse) {
						cachedRepsonses[cachedResponseKey] = [res, xhr];
					}
					resolve([res, xhr]);
				} else {
					if (xhr.status === 401) {
						Config.fetch();
					}

					reject([res, xhr]);
				}
			});
		});
	},

	performControllerRequest: function (method, args) {
		var controllerKey = (Config.user || {}).controller_key;
		return this.performRequest(method, extend({}, args, {
			middleware: [
				BasicAuthMiddleware("", controllerKey)
			],
			url: this.endpoints.cluster_controller + args.url
		}));
	},

	login: function (token) {
		return this.performRequest('POST', {
			url: this.endpoints.login,
			body: {
				token: token
			},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			return Config.fetch().then(function () {
				return args;
			});
		});
	},

	logout: function () {
		return this.performRequest('DELETE', {
			url: this.endpoints.logout
		}).then(function (args) {
			return Config.fetch().then(function () {
				return args;
			});
		});
	},

	ping: function (endpoint, protocol) {
		endpoint = window.location.host.replace("dashboard", endpoint);
		endpoint = protocol + "://" + endpoint + "/ping";

		return this.performRequest('GET', {
			url: endpoint
		});
	},

	getApps: function () {
		return this.performControllerRequest('GET', {
			url: "/apps"
		});
	},

	getApp: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId)
		});
	},

	getAppRelease: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/release"
		});
	},

	getAppFormation: function (appId, releaseId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/formations/"+ encodeURIComponent(releaseId)
		});
	},

	getAppJobs: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/jobs"
		});
	},

	getAppRoutes: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes"
		});
	},

	getAppResources: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/resources"
		});
	},

	createAppRoute: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		}).catch(function (args) {
			var res = null;
			var xhr = args[1];
			if (xhr.getResponseHeader('Content-Type').match(/json/)) {
				res = args[0];
			}
			Dispatcher.dispatch({
				name: 'CREATE_APP_ROUTE_FAILED',
				appID: appId,
				routeDomain: data.domain,
				status: xhr.status,
				error: res
			});
		});
	},

	deleteAppRoute: function (appId, routeType, routeId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ appId +"/routes/"+ routeType +"/"+ routeId
		}).catch(function (args) {
			var xhr = args[1];
			Dispatcher.dispatch({
				name: 'DELETE_APP_ROUTE_FAILED',
				appID: appId,
				routeID: routeId,
				status: xhr.status
			});
		});
	},

	createApp: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	updateApp: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId),
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deleteApp: function (appId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ encodeURIComponent(appId)
		}).catch(function (args) {
			var res = null;
			var xhr = args[1];
			if (xhr.getResponseHeader('Content-Type').match(/json/)) {
				res = args[0];
			}
			Dispatcher.dispatch({
				name: 'DELETE_APP_FAILED',
				appID: appId,
				status: xhr.status,
				error: res
			});
		});
	},

	createArtifact: function (data) {
		return this.performControllerRequest('POST', {
			url: "/artifacts",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createRelease: function (data) {
		return this.performControllerRequest('POST', {
			url: "/releases",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deployAppRelease: function (appID, releaseID, timeout) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ appID +"/deploy",
			body: {id: releaseID},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			var res = args[0];
			if (res.finished_at) {
				return args;
			}
			return this.waitForDeployment(appID, res.id, timeout).then(function () {
				return args;
			});
		}.bind(this));
	},

	waitForDeployment: function (appID, deploymentID, timeout) {
		if ( !window.hasOwnProperty('EventSource') ) {
			return Promise.reject('window.EventSource not defined');
		}

		var controllerKey = (Config.user || {}).controller_key;
		var url = this.endpoints.cluster_controller +'/events';
		url = url + QueryParams.serializeParams([{
			key: controllerKey,
			app_id: appID,
			object_types: 'deployment',
			object_id: deploymentID,
			past: 'true'
		}]);
		return new Promise(function (resolve, reject) {
			var es = new window.EventSource(url, {withCredentials: true});

			setTimeout(function () {
				reject("Timed out waiting for deployment completion");
				es.close();
			}, (timeout || Config.DEFAULT_DEPLOY_TIMEOUT) * 1000); // convert timeout from seconds to milliseconds

			es.addEventListener("error", function (e) {
				reject(e);
				es.close();
			});
			es.addEventListener("complete", function () {
				resolve();
				es.close();
			});
			es.addEventListener("message", function (e) {
				var res = JSON.parse(e.data);
				if (res.data.status === "complete") {
					resolve();
					es.close();
				}
			});
		}.bind(this));
	},

	openEventStream: function (retryCount) {
		if ( !window.hasOwnProperty('EventSource') ) {
			return Promise.reject('window.EventSource not defined');
		}

		if (this.__eventStream && this.__eventStream.readyState === 1) {
			// Already open
			return Promise.resolve();
		}

		var controllerKey = (Config.user || {}).controller_key;
		var url = this.endpoints.cluster_controller +'/events';
		url = url + QueryParams.serializeParams([{
			key: controllerKey
		}]);
		var open = false;
		var retryTimeout = null;
		var taffyAppID = null;
		var waitForTaffy = new Promise(function (resolve) {
			resolve(this.getApp('taffy').then(function (args) {
				var res = args[0];
				taffyAppID = res.id;
			}));
		}.bind(this));
		return new Promise(function (resolve, reject) {
			var es = new window.EventSource(url, {withCredentials: true});
			this.__eventStream = es;
			var handleError = function (e) {
				if ( !open && (!retryCount || retryCount < 3) ) {
					clearTimeout(retryTimeout);
					retryTimeout = setTimeout(function () {
						this.openEventStream((retryCount || 0) + 1);
					}.bind(this), 300);
				} else {
					reject(e);
				}
			}.bind(this);
			es.addEventListener("open", function () {
				open = true;
			});
			es.addEventListener("error", function (e) {
				es.close();
				handleError(e);
			});
			es.addEventListener("complete", function () {
				resolve();
				es.close();
			});
			es.addEventListener("message", function (e) {
				waitForTaffy.then(function () {
					var res = JSON.parse(e.data);
					if (res.app === taffyAppID) {
						res.taffy = true;
					}
					Dispatcher.handleServerEvent(res);
				});
			});
		}.bind(this));
	},

	getEvents: function (params) {
		return this.performControllerRequest('GET', {
			url: '/events',
			params: [ params ],
			headers: {
				'Accept': 'application/json'
			}
		});
	},

	getEvent: function (eventID) {
		return this.performControllerRequest('GET', {
			url: '/events/'+ encodeURIComponent(eventID)
		});
	},

	createAppRelease: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/release",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createAppFormation: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/formations/"+ data.release,
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	getTaffyRelease: function () {
		return this.performControllerRequest('GET', {
			cacheResponse: true, // response doesn't change
			url: "/apps/taffy/release"
		});
	},

	createTaffyJob: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps/taffy/jobs",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	listProviders: function () {
		return this.performControllerRequest('GET', {
			url: '/providers'
		});
	},

	listResources: function () {
		return this.performControllerRequest('GET', {
			url: '/resources'
		});
	},

	listProviderResources: function (providerID) {
		return this.performControllerRequest('GET', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources'
		});
	},

	getResource: function (providerID, resourceID) {
		return this.performControllerRequest('GET', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources/'+ encodeURIComponent(resourceID)
		});
	},

	addResourceApp: function (providerID, resourceID, appID) {
		return this.performControllerRequest('PUT', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources/'+ encodeURIComponent(resourceID) +'/apps/'+ encodeURIComponent(appID)
		});
	},

	deleteResourceApp: function (providerID, resourceID, appID) {
		return this.performControllerRequest('DELETE', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources/'+ encodeURIComponent(resourceID) +'/apps/'+ encodeURIComponent(appID)
		});
	},

	provisionResource: function (providerID, resourceReq) {
		resourceReq = resourceReq || {};
		resourceReq.config = resourceReq.config || {};
		return this.performControllerRequest('POST', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources',
			headers: {
				'Content-Type': 'application/json'
			},
			body: resourceReq
		}).catch(function (args) {
			var res = args[0];
			var xhr = args[1];
			Dispatcher.dispatch({
				name: 'PROVISION_RESOURCE_FAILED',
				providerID: providerID,
				error: res.message || ('Something went wrong ('+ xhr.status +')')
			});
		});
	},

	deleteResource: function (providerID, resourceID) {
		return this.performControllerRequest('DELETE', {
			url: '/providers/'+ encodeURIComponent(providerID) +'/resources/'+ resourceID
		}).catch(function (args) {
			var res = args[0];
			var xhr = args[1];
			Dispatcher.dispatch({
				name: 'DELETE_RESOURCE_FAILED',
				providerID: providerID,
				resourceID: resourceID,
				error: res.message || ('Something went wrong ('+ xhr.status +')')
			});
		});
	},

	__waitForEvent: function (fn) {
		var resolve;
		var promise = new Promise(function (rs) {
			resolve = rs;
		});
		this.__waitForEventFns.push([fn, resolve]);
		return promise;
	},

	__waitForEventWithTimeout: function (fn) {
		return Promise.race([this.__waitForEvent(fn), new Promise(function (resolve, reject) {
			setTimeout(reject, 500);
		})]);
	},

	__handleEvent: function (event) {
		var waitForEventFns = [];
		this.__waitForEventFns.forEach(function (i) {
			if (i[0](event) === true) {
				i[1]();
			} else {
				waitForEventFns.push(i);
			}
		});
		this.__waitForEventFns = waitForEventFns;

		switch (event.name) {
		case 'GET_APP':
			this.__waitForEventWithTimeout(function (e) {
				return e.name === 'APP' && e.app === event.appID;
			}).catch(function () {
				return this.getApp(event.appID).then(function (args) {
					var app = args[0];
					if (app !== null) {
						Dispatcher.dispatch({
							name: 'APP',
							app: app.id,
							data: app
						});
					} else {
						return Promise.reject(null);
					}
				});
			}.bind(this));
			break;

		case 'GET_DEPLOY_APP_JOB':
			// Ensure JOB event fires for app deploy
			// e.g. page reloaded so event won't be coming through the event stream
			this.__waitForEventWithTimeout(function (e) {
				return e.taffy === true && e.name === 'JOB' && (e.data.meta || {}).app === event.appID;
			}).catch(function () {
				return this.getAppJobs('taffy').then(function (args) {
					var res = args[0];
					var job = null;
					for (var i = 0, len = res.length; i < len; i++) {
						if ((res[i].meta || {}).app === event.appID) {
							job = res[i];
							break;
						}
					}
					if (job !== null) {
						Dispatcher.dispatch({
							name: 'JOB',
							taffy: true,
							data: job
						});
					} else {
						return Promise.reject(null);
					}
				});
			}.bind(this));
			break;

		case 'GET_APP_RELEASE':
			this.__waitForEventWithTimeout(function (e) {
				return e.app === event.appID && e.object_type === 'app_release';
			}).catch(function () {
				this.getAppRelease(event.appID).then(function (args) {
					var res = args[0];
					Dispatcher.dispatch({
						name: 'APP_RELEASE',
						app: event.appID,
						object_type: 'app_release',
						object_id: res.id,
						data: {
							release: res
						}
					});
				});
			}.bind(this));
			break;

		case 'GET_APP_FORMATION':
			this.getAppFormation(event.appID, event.releaseID).then(function (args) {
				var res = args[0];
				Dispatcher.dispatch({
					name: 'APP_FORMATION',
					app: event.appID,
					objet_type: 'formation',
					object_id: res.id,
					data: res
				});
			}).catch(function () {
				Dispatcher.dispatch({
					name: 'APP_FORMATION_NOT_FOUND',
					app: event.appID,
					data: {
						app: event.appID,
						release: event.releaseID,
						processes: {}
					}
				});
			});
			break;

		case 'GET_APP_RESOURCES':
			this.getAppResources(event.appID).then(function (args) {
				var resources = args[0];
				resources.forEach(function (r) {
					Dispatcher.dispatch({
						name: 'RESOURCE',
						app: event.appID,
						object_type: 'resources',
						object_id: r.id,
						data: r
					});
				});
				Dispatcher.dispatch({
					name: 'APP_RESOURCES_FETCHED',
					appID: event.appID
				});
			});
			break;
		}
	}
});
Beispiel #4
0
export default createClass({
	mixins: [State],

	registerWithDispatcher: function (dispatcher) {
		this.dispatcherIndex = dispatcher.register(this.handleEvent.bind(this));
	},

	willInitialize: function () {
		this.state = this.getInitialState();
		this.__changeListeners = [];
	},

	getInitialState: function () {
		return {
			installEvents: [],
			installID: null,
			domain: null,
			dashboardLoginToken: null,
			cert: null,
			certVerified: false,

			steps: [
				{ id: 'configure', label: 'Configure' },
				{ id: 'install', label: 'Install' },
				{ id: 'dashboard', label: 'Dashboard' }
			],
			currentStep: 'configure',
			completedSteps: [],

			prompt: null
		};
	},

	handleEvent: function (event) {
		switch (event.name) {
			case 'LOAD_INSTALL':
				if (this.state.installID !== event.id) {
					this.setState({
						installID: event.id,
						completedSteps: ['configure'],
						currentStep: 'install'
					});
					Client.closeEventStream();
					Client.openEventStream(event.id);
				}
				Client.checkInstallExists(event.id);
			break;

			case 'INSTALL_EXISTS':
				if ( !event.exists && (!this.state.installID || event.id === this.state.installID) ) {
					Client.closeEventStream();
					this.setState(this.getInitialState());
				}
			break;

			case 'LAUNCH_AWS':
				this.launchAWS(event);
			break;

			case 'LAUNCH_INSTALL_SUCCESS':
				this.setState({
					installID: event.res.id
				});
				Client.closeEventStream();
				Client.openEventStream(event.res.id);
			break;

			case 'LAUNCH_INSTALL_FAILURE':
				window.console.error(event);
			break;

			case 'INSTALL_PROMPT_REQUESTED':
				this.setState({
					prompt: event.data
				});
			break;

			case 'INSTALL_PROMPT_RESOLVED':
				if (this.state.prompt && this.state.prompt.id === event.data.id) {
					this.setState({
						prompt: null
					});
				}
			break;

			case 'INSTALL_PROMPT_RESPONSE':
				Client.sendPromptResponse(event.data.id, event.data);
			break;

			case 'DOMAIN':
				this.setState({
					domain: event.domain
				});
			break;

			case 'DASHBOARD_LOGIN_TOKEN':
				this.setState({
					dashboardLoginToken: event.token
				});
			break;

			case 'CA_CERT':
				this.setState({
					cert: event.cert
				});
			break;

			case 'CHECK_CERT':
				Client.checkCert(this.state.domain);
			break;

			case 'CERT_VERIFIED':
				this.setState({
					certVerified: true
				});
			break;

			case 'INSTALL_EVENT':
				this.handleInstallEvent(event.data);
			break;

			case 'INSTALL_DONE':
				if (this.state.cert && this.state.domain && this.state.dashboardLoginToken) {
					this.setState({
						completedSteps: ['configure', 'install'],
						currentStep: 'dashboard'
					});
					Client.checkCert(this.state.domain);
				}
			break;
		}
	},

	handleInstallEvent: function (data) {
		this.setState({
			installEvents: this.state.installEvents.concat([data])
		});
	},

	launchAWS: function (inputs) {
		var data = {
			creds: inputs.creds,
			region: inputs.region,
			instance_type: inputs.instanceType,
			num_instances: inputs.numInstances
		};

		if (inputs.vpcCidr) {
			data.vpc_cidr = inputs.vpcCidr;
		}

		if (inputs.subnetCidr) {
			data.subnet_cidr = inputs.subnetCidr;
		}

		this.setState(extend({}, this.getInitialState(), {
			completedSteps: ['configure'],
			currentStep: 'install'
		}));
		Client.launchInstall(data);
	}
});
Beispiel #5
0
var GithubClient = createClass({
	displayName: "GithubClient",

	mixins: [{
		ctor: {
			middleware: [
				SerializeJSONMiddleware
			]
		}
	}],

	willInitialize: function (accessToken) {
		if ( !accessToken ) {
			throw new Error(this.constructor.displayName +": Invalid client: "+ JSON.stringify(accessToken));
		}
		this.accessToken = accessToken;
	},

	performRequest: function (method, path, args) {
		args = args || {};

		if ( !path ) {
				var err = new Error(this.constructor.displayName +".prototype.performRequest(): Can't make request without path");
			setTimeout(function () {
				throw err;
			}.bind(this), 0);
			return Promise.reject(err);
		}

		var middleware = args.middleware || [];
		delete args.middleware;

		middleware = middleware.concat([{
			willSendRequest: function (request) {
				request.setRequestHeader("Authorization", "token "+ this.accessToken);
			}.bind(this)
		}]);

		return HTTP(extend({
			method: method,
			middleware: [].concat(this.constructor.middleware).concat(middleware),
			headers: extend({
				Accept: 'application/json'
			}, args.headers || {}),
			url: "https://api.github.com" + path
		}, args)).then(function (args) {
			var res = args[0];
			var xhr = args[1];
			return new Promise(function (resolve, reject) {
				if (xhr.status >= 200 && xhr.status < 400) {
					resolve([res, xhr]);
				} else {
					if (xhr.status === 401) {
						Dispatcher.handleAppEvent({
							name: "GITHUB_AUTH_CHANGE",
							authenticated: false
						});
					}
					reject([res, xhr]);
				}
			});
		});
	},

	getUser: function () {
		return this.performRequest('GET', '/user');
	},

	getRepo: function (owner, repo) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo));
	},

	getRepoTree: function (owner, repo, ref, params) {
		params = params || [{}];
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/git/trees/'+ encodeURIComponent(ref), {
			params: params
		});
	},

	getOrgs: function () {
		return this.performRequest('GET', '/user/orgs');
	},

	getRepos: function (params) {
		return this.performRequest('GET', '/user/repos', { params: params });
	},

	getStarredRepos: function (params) {
		return this.performRequest('GET', '/user/starred', { params: params });
	},

	getOrgRepos: function (params) {
		params = [].concat(params);
		params[0] = extend({}, params[0]);
		var org = params[0].org;
		delete params[0].org;
		return this.performRequest('GET', '/orgs/'+ encodeURIComponent(org) +'/repos', {params: params});
	},

	getPulls: function (owner, repo, params) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/pulls', {params:params});
	},

	getPull: function (owner, repo, number) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/pulls/'+ encodeURIComponent(number));
	},

	mergePull: function (owner, repo, number, message) {
		return this.performRequest('PUT', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/pulls/'+ encodeURIComponent(number) +'/merge', {
			headers: {
				'Content-Type': 'application/json'
			},
			body: {
				commit_message: message
			}
		});
	},

	getBranches: function (owner, repo, params) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/branches', {params:params});
	},

	getCommits: function (owner, repo, params) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/commits', {params:params});
	},

	getCommit: function (owner, repo, sha) {
		return this.performRequest('GET', '/repos/'+ encodeURIComponent(owner) +'/'+ encodeURIComponent(repo) +'/commits/'+ encodeURIComponent(sha));
	}
});
Beispiel #6
0
var Cluster = createClass({
	willInitialize: function (attrs) {
		this.__installState = Object.create(State);
		this.__installState.state = this.__computeInstallState(attrs);
		this.__installState.__changeListeners = [];
		this.__parseAttributes(attrs);
	},

	__computeInstallState: function (attrs) {
		var prevState = this.getInstallState() || {
			logEvents: [],
			certVerified: false,
			domainName: null,
			caCert: null,
			dashboardLoginToken: null,
			prompt: null,
			deleting: false,
			failed: false,
			errorDismissed: false
		};
		var state = {
			inProgress: false,
			deleting: prevState.deleting,
			failed: prevState.failed,
			errorDismissed: prevState.errorDismissed,
			logEvents: prevState.logEvents,
			steps: [
				{ id: 'configure', label: 'Configure', complete: false },
				{ id: 'install',   label: 'Install',   complete: false },
				{ id: 'dashboard', label: 'Dashboard', complete: false }
			],
			currentStep: 'configure',
			certVerified: prevState.certVerified,

			domainName: attrs.domain ? attrs.domain.domain : prevState.domainName,
			caCert: attrs.ca_cert ? window.btoa(attrs.ca_cert) : prevState.caCert,
			dashboardLoginToken: attrs.dashboard_login_token || prevState.dashboardLoginToken,

			prompt: prevState.prompt
		};

		switch (attrs.state) {
			case 'starting':
				state.currentStep = 'install';
				state.inProgress = true;
			break;

			case 'deleting':
				state.currentStep = 'install';
				state.deleting = true;
			break;

			case 'error':
				state.failed = true;
				state.currentStep = 'install';
			break;

			case 'running':
				state.currentStep = 'dashboard';
			break;
		}

		if (attrs.errorDismissed) {
			state.errorDismissed = true;
		}

		var complete = true;
		state.steps.forEach(function (step) {
			if (step.id === state.currentStep) {
				complete = false;
			}
			step.complete = complete;
		});

		return state;
	},

	getInstallState: function () {
		return this.__installState.state;
	},

	toJSON: function () {
		var data = {};
		var jsonFields = this.constructor.jsonFields;
		for (var k in jsonFields) {
			if (this.hasOwnProperty(jsonFields[k])) {
				data[k] = this[jsonFields[k]];
			}
		}
		return data;
	},

	__setInstallState: function (newState) {
		this.__installState.setState(newState);
	},

	__parseAttributes: function (attrs) {
		var jsonFields = this.constructor.jsonFields;
		for (var k in jsonFields) {
			if (attrs.hasOwnProperty(k)) {
				this[jsonFields[k]] = attrs[k];
			}
		}
		this.name = this.ID;
	},

	addChangeListener: function () {
		this.__installState.addChangeListener.apply(this.__installState, arguments);
	},

	removeChangeListener: function () {
		this.__installState.removeChangeListener.apply(this.__installState, arguments);
	},

	handleEvent: function (event) {
		switch (event.name) {
			case 'LOG':
				this.__addLog(event.data);
			break;

			case 'INSTALL_ERROR':
				this.__addLog({
					description: 'Error: '+ event.message
				});
			break;

			case 'INSTALL_ERROR_DISMISS':
				this.__setInstallState(
					this.__computeInstallState({state: 'error', errorDismissed: true})
				);
			break;

			case 'CLUSTER_STATE':
				this.__setInstallState(
					this.__computeInstallState({state: event.state})
				);
			break;

			case 'INSTALL_PROMPT_REQUESTED':
				this.__setInstallState({
					prompt: event.prompt
				});
			break;

			case 'INSTALL_PROMPT_RESOLVED':
				if (event.prompt.id === (this.getInstallState().prompt || {}).id) {
					this.__setInstallState({
						prompt: null
					});
				}
			break;

			case 'INSTALL_DONE':
				this.__parseAttributes(event.cluster);
				this.__setInstallState(this.__computeInstallState(event.cluster));
			break;

			case 'CERT_VERIFIED':
				this.__setInstallState({
					certVerified: true
				});
			break;
		}
	},

	__addLog: function (data) {
		var logEvents = [].concat(this.getInstallState().logEvents);
		logEvents.push(data);
		this.__setInstallState({
			logEvents: logEvents
		});
	}
});