Fix workspaces for UD (#15784)

* fix workspace list

Signed-off-by: Oleksii Orel <oorel@redhat.com>
7.20.x
Oleksii Orel 2020-01-23 15:10:07 +02:00 committed by GitHub
parent 28b42e335b
commit e8ab082be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 255 additions and 323 deletions

View File

@ -76,7 +76,6 @@ export class TemplateListController {
this.cheNotification = cheNotification;
this.devfileRegistry = devfileRegistry;
this.createWorkspaceSvc = createWorkspaceSvc;
this.devfileRegistryUrl = cheWorkspace.getWorkspaceSettings().cheWorkspaceDevfileRegistryUrl;
this.createButtonConfig = {
mainAction: {
@ -102,10 +101,20 @@ export class TemplateListController {
}]
};
this.init();
cheWorkspace.fetchWorkspaceSettings().then(() => {
const workspaceSettings = cheWorkspace.getWorkspaceSettings();
this.devfileRegistryUrl = workspaceSettings && workspaceSettings.cheWorkspaceDevfileRegistryUrl;
this.init();
});
}
private init(): void {
if (!this.devfileRegistryUrl) {
const message = 'Failed to load the devfile registry URL.';
this.cheNotification.showError(message);
this.$log.error(message);
return;
}
this.isLoading = true;
this.devfileRegistry.fetchDevfiles(this.devfileRegistryUrl).then((devfiles: Array<IDevfileMetaData>) => {
this.devfiles = devfiles.map(devfile => {

View File

@ -27,8 +27,8 @@
<!-- Template header -->
<div flex="100" layout="row" layout-align="start center" class="template-header">
<div flex>
<div class="header-title">Select a Template</div>
<div class="header-description">Use a sample template to create your first workspace.</div>
<div class="header-title">Select a Sample</div>
<div class="header-description">Use a sample to create your first workspace.</div>
</div>
<div flex class="template-search">
<div flex="100" layout="row">

View File

@ -1,9 +1,10 @@
.getting-started-toolbar
div.getting-started-toolbar
border-bottom 1px solid $very-light-grey-background-color
user-select none
md-toolbar
height: 110px;
height 110px
max-height 110px
div.che-toolbar-header div:last-child
bottom initial
@ -28,15 +29,13 @@
line-height 40px !important
md-content.getting-started
background-color $very-light-grey-background-color
padding-left 10px
& > *
background-color $background-color
.getting-started-list
che-box-shadow()
margin 15px
border 15px solid $very-light-grey-background-color
border-left-width 25px
.template-header
margin 15px

View File

@ -71,6 +71,9 @@ export class CheNavBarController {
private isPermissionServiceAvailable: boolean;
private isKeycloackPresent: boolean;
private workspacesNumber: number;
private pageFactories: Array<che.IFactory>;
/**
* Default constructor
*/
@ -92,6 +95,15 @@ export class CheNavBarController {
this.chePermissions = chePermissions;
this.cheKeycloak = cheKeycloak;
this.cheService = cheService;
const handler = (workspaces: Array<che.IWorkspace>) => {
this.workspacesNumber = workspaces.length;
};
this.cheAPI.getWorkspace().addListener('onChangeWorkspaces', handler);
$scope.$on('$destroy', () => {
this.cheAPI.getWorkspace().removeListener('onChangeWorkspaces', handler);
});
}
$onInit(): void {
@ -105,8 +117,13 @@ export class CheNavBarController {
this.$scope.$broadcast('navbar-selected:set', path);
});
this.cheAPI.getWorkspace().fetchWorkspaces();
this.cheAPI.getFactory().fetchFactories();
this.cheAPI.getWorkspace().fetchWorkspaces().then((workspaces: Array<che.IWorkspace>) => {
this.workspacesNumber = workspaces.length;
});
this.cheAPI.getFactory().fetchFactories().then(() => {
this.pageFactories = this.cheAPI.getFactory().getPageFactories();
});
this.isPermissionServiceAvailable = false;
this.resolvePermissionServiceAvailability().then((isAvailable: boolean) => {
@ -167,20 +184,12 @@ export class CheNavBarController {
return fullName ? fullName : email;
}
/**
* Returns number of workspaces.
* @return {number}
*/
getWorkspacesNumber(): number {
return this.cheAPI.getWorkspace().getWorkspaces().length;
}
/**
* Returns number of factories.
* @return {number}
*/
getFactoriesNumber(): number {
return this.cheAPI.getFactory().getPageFactories().length;
return this.pageFactories.length;
}
/**

View File

@ -55,8 +55,8 @@
<div class="navbar-item" layout="row" layout-align="start center" id="workspaces-item">
<md-icon md-font-icon="navbar-icon chefont cheico-workspace"></md-icon>
<span>Workspaces</span>
<span class="navbar-number" ng-show="navbarController.getWorkspacesNumber()">
&nbsp;({{navbarController.getWorkspacesNumber()}})
<span class="navbar-number" ng-show="navbarController.workspacesNumber">
&nbsp;({{navbarController.workspacesNumber}})
</span>
</div>
</md-button>

View File

@ -92,6 +92,15 @@ export class NavbarRecentWorkspacesController {
this.dropdownItems = {};
this.dropdownItemTempl = [];
const handler = (workspaces: Array<che.IWorkspace>) => {
this.workspaces = workspaces;
this.updateRecentWorkspaces();
};
this.cheWorkspace.addListener('onChangeWorkspaces', handler);
$scope.$on('$destroy', () => {
this.cheWorkspace.removeListener('onChangeWorkspaces', handler);
});
let cleanup = $rootScope.$on('recent-workspace:set', (event: ng.IAngularEvent, workspaceId: string) => {
this.veryRecentWorkspaceId = workspaceId;
this.updateRecentWorkspaces();
@ -99,22 +108,14 @@ export class NavbarRecentWorkspacesController {
$rootScope.$on('$destroy', () => {
cleanup();
});
$scope.$watch(() => {
return this.workspaces;
}, () => {
this.updateRecentWorkspaces();
}, true);
}
$onInit(): void {
this.workspaceCreationLink = this.cheBranding.getWorkspace().creationLink;
// get workspaces
this.workspaces = this.cheWorkspace.getWorkspaces();
// fetch workspaces when initializing
this.cheWorkspace.fetchWorkspaces();
this.cheWorkspace.fetchWorkspaces().then(() => {
this.workspaces = this.cheWorkspace.getWorkspaces();
});
this.updateRecentWorkspaces();
this.fetchWorkspaceSettings();

View File

@ -193,8 +193,6 @@ export class ListOrganizationWorkspacesController {
/**
* Filter workspaces by namespace.
*
* @returns {Array<che.IWorkspace>}
*/
filterWorkspacesByNamespace(): Array<che.IWorkspace> {
return this.lodash.filter(this.cheWorkspace.getWorkspaces(), (workspace: che.IWorkspace) => {

View File

@ -90,7 +90,7 @@ export class ListWorkspacesCtrl {
// map of workspaces' used resources (consumed GBH):
this.workspaceUsedResources = new Map();
this.getUserWorkspaces();
this.fetchUserWorkspaces();
this.cheNamespaceRegistry.fetchNamespaces().then(() => {
this.namespaceLabels = this.getNamespaceLabelsList();
@ -123,69 +123,26 @@ export class ListWorkspacesCtrl {
}
/**
* Fetch current user's workspaces (where he is a member):
* Fetch current user's workspaces.
*/
getUserWorkspaces(): void {
// fetch workspaces when initializing
fetchUserWorkspaces(): void {
const promise = this.cheAPI.getWorkspace().fetchWorkspaces();
promise.then(() => {
return this.updateSharedWorkspaces();
this.userWorkspaces = this.cheAPI.getWorkspace().getWorkspaces();
return this.$q.resolve();
}, (error: any) => {
if (error && error.status === 304) {
// ok
return this.updateSharedWorkspaces();
this.userWorkspaces = this.cheAPI.getWorkspace().getWorkspaces();
return this.$q.resolve();
}
this.state = 'error';
this.isInfoLoading = false;
return this.$q.reject(error);
}).then(() => {
this.cheListHelper.setList(this.userWorkspaces, 'id');
});
}
/**
* Update the info of all user workspaces:
*
* @return {IPromise<any>}
*/
updateSharedWorkspaces(): ng.IPromise<any> {
this.userWorkspaces = [];
let workspaces = this.cheAPI.getWorkspace().getWorkspaces();
if (workspaces.length === 0) {
}).finally(()=> {
this.isInfoLoading = false;
}
const promises: Array<ng.IPromise<any>> = [];
workspaces.forEach((workspace: che.IWorkspace) => {
// first check the list of already received workspace info:
if (!this.workspacesById.get(workspace.id)) {
const promise = this.cheWorkspace.fetchWorkspaceDetails(workspace.id)
.catch((error: any) => {
if (error && error.status === 304) {
return this.$q.when();
}
let message = error.data && error.data.message ? ' Reason: ' + error.data.message : '';
let workspaceName = this.cheWorkspace.getWorkspaceDataManager().getName(workspace);
this.cheNotification.showError('Failed to retrieve workspace ' + workspaceName + ' data.' + message) ;
return this.$q.reject(error);
})
.then(() => {
let userWorkspace = this.cheAPI.getWorkspace().getWorkspaceById(workspace.id);
this.getWorkspaceInfo(userWorkspace);
this.userWorkspaces.push(userWorkspace);
return this.$q.when();
});
promises.push(promise);
} else {
let userWorkspace = this.workspacesById.get(workspace.id);
this.userWorkspaces.push(userWorkspace);
this.isInfoLoading = false;
}
});
this.state = 'loaded';
return this.$q.all(promises);
}
/**
@ -199,39 +156,13 @@ export class ListWorkspacesCtrl {
});
}
/**
* Gets all necessary workspace info to be displayed.
*
* @param {che.IWorkspace} workspace
*/
getWorkspaceInfo(workspace: che.IWorkspace): void {
let promises = [];
this.workspacesById.set(workspace.id, workspace);
workspace.isLocked = false;
workspace.usedResources = this.workspaceUsedResources.get(workspace.id);
// no access to runner resources if workspace is locked:
if (!workspace.isLocked) {
let promiseWorkspace = this.cheAPI.getWorkspace().fetchWorkspaceDetails(workspace.id);
promises.push(promiseWorkspace);
}
this.$q.all(promises).finally(() => {
this.isInfoLoading = false;
});
}
/**
* Delete all selected workspaces
*/
deleteSelectedWorkspaces(): void {
const selectedWorkspaces = this.cheListHelper.getSelectedItems(),
selectedWorkspacesIds = selectedWorkspaces.map((workspace: che.IWorkspace) => {
return workspace.id;
});
const selectedWorkspaces = this.cheListHelper.getSelectedItems();
let queueLength = selectedWorkspacesIds.length;
let queueLength = selectedWorkspaces.length;
if (!queueLength) {
this.cheNotification.showError('No such workspace.');
return;
@ -244,26 +175,22 @@ export class ListWorkspacesCtrl {
let deleteWorkspacePromises = [];
let workspaceName;
selectedWorkspacesIds.forEach((workspaceId: string) => {
this.cheListHelper.itemsSelectionStatus[workspaceId] = false;
selectedWorkspaces.forEach((workspace: che.IWorkspace) => {
this.cheListHelper.itemsSelectionStatus[workspace.id] = false;
let workspace = this.cheWorkspace.getWorkspaceById(workspaceId);
if (!workspace) {
return;
}
workspaceName = this.cheWorkspace.getWorkspaceDataManager().getName(workspace);
let stoppedStatusPromise = this.cheWorkspace.fetchStatusChange(workspaceId, 'STOPPED');
let stoppedStatusPromise = this.cheWorkspace.fetchStatusChange(workspace.id, 'STOPPED');
// stop workspace if it's status is RUNNING
if (workspace.status === 'RUNNING') {
this.cheWorkspace.stopWorkspace(workspaceId);
this.cheWorkspace.stopWorkspace(workspace.id);
}
// delete stopped workspace
let promise = stoppedStatusPromise.then(() => {
return this.cheWorkspace.deleteWorkspace(workspaceId);
return this.cheWorkspace.deleteWorkspace(workspace.id);
}).then(() => {
this.workspacesById.delete(workspaceId);
this.workspacesById.delete(workspace.id);
queueLength--;
},
(error: any) => {
@ -274,7 +201,7 @@ export class ListWorkspacesCtrl {
});
this.$q.all(deleteWorkspacePromises).finally(() => {
this.getUserWorkspaces();
this.fetchUserWorkspaces();
if (isError) {
this.cheNotification.showError('Delete failed.');

View File

@ -16,9 +16,11 @@
A workspace is where your projects live and run. Create workspaces from stacks that define projects, runtimes, and commands.
</che-description>
<md-content md-scroll-y flex layout="column" md-theme="maincontent-theme">
<div class="progress-line"><md-progress-linear md-mode="indeterminate"
ng-show="listWorkspacesCtrl.isInfoLoading || listWorkspacesCtrl.isRequestPending"></md-progress-linear>
<md-content flex class="workspace-list-content" ng-hide="listWorkspacesCtrl.isInfoLoading"><div>
<div class="progress-line">
<md-progress-linear md-mode="indeterminate"
ng-show="listWorkspacesCtrl.isInfoLoading"></md-progress-linear>
</div>
<md-content flex class="workspace-list-content" ng-hide="listWorkspacesCtrl.isInfoLoading">
<che-list-header che-input-placeholder="Search"
che-on-search-change="listWorkspacesCtrl.onSearchChanged(str)"
che-hide-search="listWorkspacesCtrl.userWorkspaces.length === 0"
@ -64,15 +66,14 @@
</div>
</div>
</che-list-header>
<che-list ng-show="listWorkspacesCtrl.cheListHelper.visibleItemsNumber > 0">
<che-list>
<che-workspace-item
ng-repeat="workspace in listWorkspacesCtrl.cheListHelper.getVisibleItems() | orderBy:[listWorkspacesCtrl.workspaceOrderBy, 'devfile.metadata.name']"
ng-model="listWorkspacesCtrl.cheListHelper.itemsSelectionStatus[workspace.id]"
is-request-pending="listWorkspacesCtrl.isRequestPending"
che-selectable="true"
che-display-labels="false"
che-on-checkbox-click="listWorkspacesCtrl.cheListHelper.updateBulkSelectionStatus()"
che-workspace-item="workspace"></che-workspace-item>
ng-repeat="workspace in listWorkspacesCtrl.cheListHelper.getVisibleItems() | orderBy:[listWorkspacesCtrl.workspaceOrderBy, 'devfile.metadata.name']"
ng-model="listWorkspacesCtrl.cheListHelper.itemsSelectionStatus[workspace.id]"
che-selectable="true"
che-display-labels="false"
che-on-checkbox-click="listWorkspacesCtrl.cheListHelper.updateBulkSelectionStatus()"
che-workspace-item="workspace"></che-workspace-item>
</che-list>
<div class="che-list-empty">
<span ng-show="listWorkspacesCtrl.userWorkspaces.length > 0 && listWorkspacesCtrl.cheListHelper.visibleItemsNumber === 0">

View File

@ -10,10 +10,10 @@
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {CheWorkspace} from '../../../../components/api/workspace/che-workspace.factory';
import {CheWorkspace, WorkspaceStatus} from '../../../../components/api/workspace/che-workspace.factory';
import {CheBranding} from '../../../../components/branding/che-branding.factory';
import {WorkspacesService} from '../../workspaces.service';
import { WorkspaceDataManager } from '../../../../components/api/workspace/workspace-data-manager';
import {WorkspaceDataManager} from '../../../../components/api/workspace/workspace-data-manager';
const BLUR_TIMEOUT = 5000;
@ -34,6 +34,7 @@ export class WorkspaceItemCtrl {
'cheWorkspace',
'lodash',
'workspacesService',
'$filter'
];
$document: ng.IDocumentService;
@ -47,6 +48,7 @@ export class WorkspaceItemCtrl {
workspace: che.IWorkspace;
workspaceName: string;
workspaceSupportIssues: any;
$filter: ng.IFilterService;
private supportedVersionTypeIssue: any;
private timeoutPromise: ng.IPromise<any>;
@ -63,6 +65,7 @@ export class WorkspaceItemCtrl {
cheWorkspace: CheWorkspace,
lodash: any,
workspacesService: WorkspacesService,
$filter: ng.IFilterService,
) {
this.$document = $document;
this.$location = $location;
@ -70,6 +73,7 @@ export class WorkspaceItemCtrl {
this.cheWorkspace = cheWorkspace;
this.lodash = lodash;
this.workspacesService = workspacesService;
this.$filter = $filter;
this.workspaceDataManager = new WorkspaceDataManager();
@ -123,12 +127,12 @@ export class WorkspaceItemCtrl {
* Redirects to workspace details.
* @param tab {string}
*/
redirectToWorkspaceDetails(tab?: string): void {
this.$location.path('/workspace/' + this.workspace.namespace + '/' + this.workspaceName).search({tab: tab ? tab : 'Overview'});
redirectToWorkspaceDetails(tab: string = 'Overview'): void {
this.$location.path(`/workspace/${this.workspace.namespace}/${this.workspaceName}`).search({tab});
}
getMemoryLimit(workspace: che.IWorkspace): string {
return '-';
getMemoryLimit(): string {
return '-';
}
setTemporaryFocus(elementId?: string): void {
@ -151,12 +155,28 @@ export class WorkspaceItemCtrl {
}
}
get workspaceTooltip(): string {
const isWorkspaceRunning = WorkspaceStatus.RUNNING === WorkspaceStatus[this.getWorkspaceStatus()];
const attributes = this.workspace.attributes;
const time = parseInt('' + (attributes.updated ? attributes.updated : attributes.created), 10);
const amTimeAgo: (time: number) => string = this.$filter('amTimeAgo');
return isWorkspaceRunning ? 'Running' : 'Last modified: ' + amTimeAgo(time);
}
/**
* Returns current status of workspace
* @returns {String}
*/
getWorkspaceStatus(): string {
let workspace = this.cheWorkspace.getWorkspaceById(this.workspace.id);
return workspace ? workspace.status : 'unknown';
const workspace = this.cheWorkspace.getWorkspaceById(this.workspace.id);
if (!workspace || !workspace.status) {
return 'unknown';
}
return workspace.status;
}
isCheckboxEnable(): boolean {
const status = WorkspaceStatus[this.getWorkspaceStatus()];
return status === WorkspaceStatus.RUNNING || status === WorkspaceStatus.STOPPED;
}
}

View File

@ -11,7 +11,7 @@
Red Hat, Inc. - initial API and implementation
-->
<che-list-item flex ng-mouseover="hover=true" ng-mouseout="hover=false">
<che-list-item flex>
<div flex="100"
id="ws-name-{{workspaceItemCtrl.workspaceName}}"
data-ws-status="{{workspaceItemCtrl.getWorkspaceStatus()}}"
@ -21,9 +21,9 @@
<div layout="row"
layout-align="start center"
class="che-checkbox-area"
ng-if="workspaceItemCtrl.isSelectable === true">
ng-if="workspaceItemCtrl.isSelectable">
<che-list-item-checked ng-model="workspaceItemCtrl.isSelect"
ng-show="('RUNNING' === workspaceItemCtrl.getWorkspaceStatus() || 'STOPPED' === workspaceItemCtrl.getWorkspaceStatus())"
ng-show="workspaceItemCtrl.isCheckboxEnable()"
che-aria-label-checkbox="Workspace {{workspaceItemCtrl.workspaceName}}"
ng-click="workspaceItemCtrl.onCheckboxClick()"></che-list-item-checked>
</div>
@ -32,7 +32,6 @@
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<!-- Name -->
<div flex-gt-xs="25"
layout="row"
@ -40,25 +39,25 @@
ng-click="workspaceItemCtrl.redirectToWorkspaceDetails();">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<div layout="row" flex>
<workspace-status-indicator flex="none" che-status="workspaceItemCtrl.getWorkspaceStatus()"></workspace-status-indicator>
<div class="workspace-name-clip" id="ws-full-name-{{workspaceItemCtrl.workspace.namespace}}/{{workspaceItemCtrl.workspaceName}}">
<span uib-tooltip="{{'RUNNING' === workspaceItemCtrl.getWorkspaceStatus() ? 'Running' : 'Last modified: ' + (workspaceItemCtrl.workspace.attributes.updated | amTimeAgo)}}"
<workspace-status-indicator che-status="workspaceItemCtrl.getWorkspaceStatus()"
flex="none"></workspace-status-indicator>
<div class="workspace-name-clip"
id="ws-full-name-{{workspaceItemCtrl.workspace.namespace}}/{{workspaceItemCtrl.workspaceName}}">
<span uib-tooltip="{{workspaceItemCtrl.workspaceTooltip}}"
class="che-hover">
<che-clip-the-middle>{{workspaceItemCtrl.workspace.namespace}}/{{workspaceItemCtrl.workspaceName}}</che-clip-the-middle>
{{workspaceItemCtrl.workspace.namespace}}/{{workspaceItemCtrl.workspaceName}}
</span>
</div>
</div>
<i class="fa fa-clock-o workspace-item-temp" ng-if="workspaceItemCtrl.workspace.temporary"></i>
</div>
<!-- RAM -->
<div flex-gt-xs="10"
class="che-list-item-name workspace-item-ram"
ng-click="workspaceItemCtrl.redirectToWorkspaceDetails();">
<span class="che-xs-header noselect" hide-gt-xs>RAM</span>
<span class="workspace-consumed-value" name="workspace-ram-value">{{workspaceItemCtrl.getMemoryLimit(workspaceItemCtrl.workspace)}}</span>
<span class="workspace-consumed-value" name="workspace-ram-value">{{workspaceItemCtrl.getMemoryLimit()}}</span>
</div>
<!-- Projects -->
<div flex-gt-xs="10"
class="che-list-item-name workspace-item-projects"
@ -66,12 +65,11 @@
<span class="che-xs-header noselect" hide-gt-xs>Projects</span>
<span class="che-hover" name="workspace-projects-value">
{{workspaceItemCtrl.projects.length > 0 ? workspaceItemCtrl.projects.length : '-'}}
<span ng-show="workspaceItemCtrl.displayLabels && (workspaceItemCtrl.projects.length > 0) === true">
project<span ng-if="workspaceItemCtrl.workspace.config.projects.length > 1">s</span>
<span ng-show="workspaceItemCtrl.displayLabels && workspaceItemCtrl.projects.length">
project<span ng-if="workspaceItemCtrl.workspace.projects.length > 1">s</span>
</span>
</span>
</div>
<!-- Stack ID -->
<div flex-gt-xs="40"
class="che-list-item-name workspace-item-stack"
@ -79,7 +77,6 @@
<span class="che-xs-header noselect" hide-gt-xs>Stack</span>
<span class="che-hover" name="workspace-stacks-name">{{workspaceItemCtrl.stackDescription}}</span>
</div>
<!-- Actions -->
<div flex-gt-xs="15"
class="workspace-item-actions">
@ -90,11 +87,11 @@
workspace-id="workspaceItemCtrl.workspace.id"
name="workspace-stop-start-button"
is-request-pending="workspaceItemCtrl.isRequestPending"></che-workspace-status>
<a href="#/workspace/{{workspaceItemCtrl.workspace.namespace}}/{{workspaceItemCtrl.workspaceName}}?tab={{workspaceItemCtrl.workspace.devfile ? 'Devfile':'Config'}}"
<span ng-click="workspaceItemCtrl.redirectToWorkspaceDetails('Devfile');"
name="configure-workspace-button"
uib-tooltip="Configure workspace">
<span class="fa fa-cog"></span>
</a>
</span>
<span ng-if="workspaceItemCtrl.isSupported"
ng-click="workspaceItemCtrl.redirectToWorkspaceDetails('Projects');"
uib-tooltip="Add project">

View File

@ -28,7 +28,6 @@ import {CheWorkspaceStatusButton} from './status-button/workspace-status-button.
import {WorkspaceDetailsOverviewController} from './workspace-overview/workspace-details-overview.controller';
import {WorkspaceDetailsOverview} from './workspace-overview/workspace-details-overview.directive';
import {CheWorkspace} from '../../../components/api/workspace/che-workspace.factory';
import {WorkspaceConfigService} from '../workspace-config.service';
import {CheProjectItem} from './workspace-projects/project-item/project-item.directive';
import {ProjectItemCtrl} from './workspace-projects/project-item/project-item.controller';
import {NoGithubOauthDialogController} from '../create-workspace/ready-to-go-stacks/project-source-selector/add-import-project/import-github-project/oauth-dialog/no-github-oauth-dialog.controller';
@ -80,10 +79,16 @@ export class WorkspaceDetailsConfig {
controller: 'WorkspaceDetailsController',
controllerAs: 'workspaceDetailsController',
resolve: {
initData: ['$q', '$route', 'cheWorkspace', 'workspaceConfigService', ($q: ng.IQService, $route: ng.route.IRouteService, cheWorkspace: CheWorkspace, workspaceConfigService: WorkspaceConfigService) => {
return workspaceConfigService.resolveWorkspaceRoute().then(() => {
const {namespace, workspaceName} = $route.current.params;
const workspaceDetails = cheWorkspace.getWorkspaceByName(namespace, workspaceName);
initData: ['$q', '$route', 'cheWorkspace', ($q: ng.IQService, $route: ng.route.IRouteService, cheWorkspace: CheWorkspace) => {
const {namespace, workspaceName} = $route.current.params;
const getName = (workspace: che.IWorkspace) => {
if (workspace.config) {
return workspace.config.name;
}
return workspace.devfile.metadata.name;
};
return cheWorkspace.fetchWorkspacesByNamespace(namespace).then(() => {
const workspaceDetails = cheWorkspace.getWorkspacesByNamespace(namespace).find( workspace => getName(workspace) === workspaceName);
return {namespaceId: namespace, workspaceName: workspaceName, workspaceDetails: workspaceDetails};
});
}]

View File

@ -27,6 +27,9 @@ div.workspace-details-warning-info
background-color $warning-color
.workspace-details-content
& > md-tabs
height 100%
md-tab-content
padding 0

View File

@ -45,7 +45,7 @@ export class CheFactory {
private factoryContentsByWorkspaceId: Map<string, any>;
private pageFactories: Array<che.IFactory>;
private factoryPagesMap: Map<number, any>;
private pageInfo: any;
private pageInfo: {currentPageNumber: number, countOfPages: number} = {currentPageNumber: 1, countOfPages: 1};
private itemsPerPage: number;
private cheUser: CheUser;
@ -70,7 +70,6 @@ export class CheFactory {
// paging
this.pageFactories = [];
this.factoryPagesMap = new Map();
this.pageInfo = {};
this.remoteFactoryAPI = <IFactoriesResource<any>>this.$resource('/api/factory/:factoryId', {factoryId: '@id'}, {
updateFactory: {method: 'PUT', url: '/api/factory/:factoryId'},
@ -96,26 +95,6 @@ export class CheFactory {
_getPageFromResponse(data: any, headersLink: any): any {
let links = new Map();
if (!headersLink) {
// TODO remove it after adding headers paging links on server side
let user = this.cheUser.getUser();
if (!this.itemsPerPage || !user) {
return {factories: data};
}
this.pageInfo.currentPageNumber = this.pageInfo.currentPageNumber ? this.pageInfo.currentPageNumber : 1;
let link = '/api/factory/find?creator.userId=' + user.id + '&maxItems=' + this.itemsPerPage;
links.set('first', link + '&skipCount=0');
if (data && data.length > 0) {
links.set('next', link + '&skipCount=' + this.pageInfo.currentPageNumber * this.itemsPerPage);
}
if (this.pageInfo.currentPageNumber > 1) {
links.set('prev', link + '&skipCount=' + (this.pageInfo.currentPageNumber - 2) * this.itemsPerPage);
}
return {
factories: data,
links: links
};
}
let pattern = new RegExp('<([^>]+?)>.+?rel="([^"]+?)"', 'g');
let result;
while (result = pattern.exec(headersLink)) {
@ -253,8 +232,8 @@ export class CheFactory {
}
/**
* Ask for loading the factories page in asynchronous way
* If there are no changes, it's not updated
* Ask for loading the factories page in asynchronous way.
* If there are no changes, page info won't be updated.
* @param pageKey - the key of page ('first', 'prev', 'next', 'last' or '1', '2', '3' ...)
* @returns {*} the promise
*/
@ -278,7 +257,7 @@ export class CheFactory {
pageNumber = this.pageInfo.countOfPages;
}
let pageData = this.factoryPagesMap.get(pageNumber);
if (pageData.link) {
if (pageData && pageData.link) {
this.pageInfo.currentPageNumber = pageNumber;
let queryData = this._getPageParamByLink(pageData.link);
if (!queryData) {
@ -316,23 +295,15 @@ export class CheFactory {
});
}
let resultPromise = deferred.promise.then((userId: string) => {
return deferred.promise.then((userId: string) => {
queryData.userId = userId;
return this.remoteFactoryAPI.getFactories(queryData).$promise.then((data: any) => {
return this._updateFactoriesDetails(data.factories).then((factoriesDetails: any) => {
data.factories = factoriesDetails;
return this.$q.when(data);
}, (error: any) => {
return this.$q.reject(error);
});
}, (error: any) => {
return this.$q.reject(error);
});
}, (error: any) => {
return this.$q.reject(error);
});
return resultPromise;
}
_updateFactoriesDetails(factories: Array<any>): ng.IPromise<any> {
@ -526,13 +497,11 @@ export class CheFactory {
* @returns {Array<string>} links acceptance links
*/
detectLinks(factory: che.IFactory): Array<string> {
let links = [];
if (!factory || !factory.links) {
const links = [];
if (!factory || !angular.isArray(factory.links)) {
return links;
}
this.lodash.find(factory.links, (link: any) => {
factory.links.forEach((link: any) => {
if (link.rel === 'accept' || link.rel === 'accept-named') {
links.push(link.href);
}
@ -567,18 +536,15 @@ export class CheFactory {
* @returns {ng.IPromise<any>} the promise
*/
setFactory(factory: che.IFactory): ng.IPromise<any> {
let deferred = this.$q.defer();
// check factory
const deferred = this.$q.defer();
if (!factory || !factory.id) {
deferred.reject({data: {message: 'Read factory error.'}});
return deferred.promise;
}
let promise = this.remoteFactoryAPI.updateFactory({factoryId: factory.id}, factory).$promise;
// check if was OK or not
promise.then((factory: any) => {
this.remoteFactoryAPI.updateFactory({factoryId: factory.id}, factory).$promise.then((factory: any) => {
factory.name = factory.name ? factory.name : '';
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
this.fetchFactoryPage(this.pageInfo.currentPageNumber.toString());
deferred.resolve(factory);
}, (error: any) => {
deferred.reject(error);
@ -594,14 +560,14 @@ export class CheFactory {
* @returns {ng.IPromise<any>} the promise
*/
setFactoryContent(factoryId: string, factoryContent: any): ng.IPromise<any> {
let deferred = this.$q.defer();
let promise = this.remoteFactoryAPI.updateFactory({factoryId: factoryId}, factoryContent).$promise;
const deferred = this.$q.defer();
const promise = this.remoteFactoryAPI.updateFactory({factoryId: factoryId}, factoryContent).$promise;
promise.then(() => {
let fetchFactoryPromise = this.fetchFactoryById(factoryId);
// check if was OK or not
fetchFactoryPromise.then((factory: any) => {
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
this.fetchFactoryPage(this.pageInfo.currentPageNumber.toString());
deferred.resolve(factory);
}, (error: any) => {
deferred.reject(error);
@ -619,37 +585,22 @@ export class CheFactory {
* @returns {ng.IPromise<any>} the promise
*/
deleteFactoryById(factoryId: string): ng.IPromise<any> {
let promise = this.remoteFactoryAPI.delete({factoryId: factoryId}).$promise;
// check if was OK or not
const promise = this.remoteFactoryAPI.delete({factoryId: factoryId}).$promise;
return promise.then(() => {
this.factoriesById.delete(factoryId);
if (this.pageInfo && this.pageInfo.currentPageNumber) {
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
}
});
}
/**
* Helper method that extract the factory ID from a factory URL
* @param factoryURL the factory URL to analyze
* @returns the stringified ID of a factory
*/
getIDFromFactoryAPIURL(factoryURL: string): string {
let index = factoryURL.lastIndexOf('/factory/');
if (index > 0) {
return factoryURL.slice(index + '/factory/'.length, factoryURL.length);
}
}
/**
* Returns the factory url based on id.
* @returns {link.href|*} link value
*/
getFactoryIdUrl(factory: che.IFactory): string {
let link = this.lodash.find(factory.links, (link: any) => {
const link = this.lodash.find(factory.links, (link: any) => {
return 'accept' === link.rel;
});
return link ? link.href : 'No value';
return link ? link.href : '';
}
/**
@ -658,10 +609,9 @@ export class CheFactory {
* @returns {link.href|*} link value
*/
getFactoryNamedUrl(factory: che.IFactory): string {
let link = this.lodash.find(factory.links, (link: any) => {
const link = this.lodash.find(factory.links, (link: any) => {
return 'accept-named' === link.rel;
});
return link ? link.href : null;
return link ? link.href : '';
}
}

View File

@ -128,7 +128,7 @@ export class CheHttpBackend {
this.$httpBackend.when('DELETE', '/api/workspace/' + key).respond(200);
}
this.$httpBackend.when('GET', '/api/workspace').respond(workspaceReturn);
this.$httpBackend.when('GET', /\/api\/workspace(\?.*$)?/).respond(workspaceReturn);
this.$httpBackend.when('GET', '/api/stack?maxItems=50').respond(this.stacks);

View File

@ -18,9 +18,6 @@ import {CheBranding} from '../../branding/che-branding.factory';
import {CheNotification} from '../../notification/che-notification.factory';
import {WorkspaceDataManager} from './workspace-data-manager';
const WS_AGENT_HTTP_LINK: string = 'wsagent/http';
const WS_AGENT_WS_LINK: string = 'wsagent/ws';
interface ICHELicenseResource<T> extends ng.resource.IResourceClass<T> {
createDevfile: any;
deleteWorkspace: any;
@ -43,14 +40,29 @@ export enum WorkspaceStatus {
ERROR
}
type workspacesEvent = 'onChangeWorkspaces' | 'onDeleteWorkspace';
const MAX_ITEMS = 256;
/**
* This class is handling the workspace retrieval
* It sets to the array workspaces the current workspaces which are not temporary
* @author Florent Benoit
* @author Oleksii Orel
*/
export class CheWorkspace {
static $inject = ['$resource', '$http', '$q', 'cheJsonRpcApi', 'cheNotification', '$websocket', '$location', 'proxySettings', 'userDashboardConfig', 'lodash', 'cheBranding'];
static $inject = ['$resource',
'$http',
'$q',
'cheJsonRpcApi',
'cheNotification',
'$websocket',
'$location',
'proxySettings',
'userDashboardConfig',
'lodash',
'cheBranding'];
private $resource: ng.resource.IResourceService;
private $http: ng.IHttpService;
@ -58,9 +70,8 @@ export class CheWorkspace {
private $websocket: any;
private cheNotification: CheNotification;
private cheJsonRpcMasterApi: CheJsonRpcMasterApi;
private listeners: Array<any>;
private workspaceStatuses: Array<string>;
private workspaces: Array<che.IWorkspace>;
private handlers: { [paramName: string]: Array<any> };
private workspaceIds: Array<string>;
private subscribedWorkspacesIds: Array<string>;
private workspacesByNamespace: Map<string, Array<che.IWorkspace>>;
private workspacesById: Map<string, che.IWorkspace>;
@ -98,8 +109,6 @@ export class CheWorkspace {
lodash: any,
cheBranding: CheBranding
) {
this.workspaceStatuses = ['RUNNING', 'STOPPED', 'PAUSED', 'STARTING', 'STOPPING', 'ERROR'];
// keep resource
this.$q = $q;
this.$resource = $resource;
this.$http = $http;
@ -109,7 +118,7 @@ export class CheWorkspace {
this.workspaceDataManager = new WorkspaceDataManager();
// current list of workspaces
this.workspaces = [];
this.workspaceIds = [];
// per Id
this.workspacesById = new Map();
@ -118,7 +127,7 @@ export class CheWorkspace {
this.workspacesByNamespace = new Map();
// listeners if workspaces are changed/updated
this.listeners = [];
this.handlers = {};
// list of subscribed to websocket workspace Ids
this.subscribedWorkspacesIds = [];
@ -152,7 +161,6 @@ export class CheWorkspace {
/**
* Add callback to the list of on workspace change subscribers.
*
* @param {string} workspaceId
* @param {IObservableCallbackFn<che.IWorkspace>} action the callback
*/
@ -170,7 +178,6 @@ export class CheWorkspace {
/**
* Unregister on workspace change callback.
*
* @param {string} workspaceId
* @param {IObservableCallbackFn<che.IWorkspace>} action the callback
*/
@ -183,20 +190,39 @@ export class CheWorkspace {
}
/**
* Add a listener that need to have the onChangeWorkspaces(workspaces: Array) method
* @param listener {Function} a changing listener
* Adds a listener on an event.
* @param {workspacesEvent} event
* @param {Function} handler
*/
addListener(listener: Function): void {
this.listeners.push(listener);
addListener(event: workspacesEvent, handler: Function): void {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(handler);
}
/**
* Removes a listener.
* @param {workspacesEvent} event
* @param {Function} handler
*/
removeListener(event: workspacesEvent, handler: Function): void {
if (!this.handlers[event] || !handler) {
return;
}
const index = this.handlers[event].indexOf(handler);
if (index === -1) {
return;
}
this.handlers[event].splice(index, 1);
}
/**
* Gets the workspaces of this remote
* @returns {Array}
*/
getWorkspaces(): Array<che.IWorkspace> {
return this.workspaces;
return this.workspaceIds.map(id => this.workspacesById.get(id));
}
/**
@ -208,18 +234,17 @@ export class CheWorkspace {
}
getWorkspaceByName(namespace: string, name: string): che.IWorkspace {
return this.lodash.find(this.workspaces, (workspace: che.IWorkspace) => {
return this.lodash.find(this.getWorkspaces(), (workspace: che.IWorkspace) => {
return workspace.namespace === namespace && this.workspaceDataManager.getName(workspace) === name;
});
}
/**
* Fetches workspaces by provided namespace.
*
* @param namespace namespace
*/
fetchWorkspacesByNamespace(namespace: string): ng.IPromise<void> {
let promise = this.$http.get('/api/workspace/namespace/' + namespace);
let promise = this.$http.get(`/api/workspace/namespace/${namespace}?maxItems=${MAX_ITEMS}`);
let resultPromise = promise.then((response: ng.IHttpResponse<che.IWorkspace[]>) => {
const workspaces = this.getWorkspacesByNamespace(namespace);
@ -258,39 +283,34 @@ export class CheWorkspace {
/**
* Ask for loading the workspaces in asynchronous way
* If there are no changes, it's not updated
* @returns {ng.IPromise<Array<che.IWorkspace>>}
*/
fetchWorkspaces(): ng.IPromise<Array<che.IWorkspace>> {
let promise = this.remoteWorkspaceAPI.query().$promise;
let updatedPromise = promise.then((data: Array<che.IWorkspace>) => {
this.workspaces.length = 0;
this.workspacesById.clear();
// add workspace if not temporary
data.forEach((workspace: che.IWorkspace) => {
let promise = this.remoteWorkspaceAPI.query({'maxItems': 256}).$promise;
promise.then((workspaces: Array<che.IWorkspace>) => {
this.workspaceIds.length = 0;
workspaces.forEach((workspace: che.IWorkspace) => {
if (!workspace.temporary) {
this.workspaceIds.push(workspace.id);
}
this.updateWorkspacesList(workspace);
});
return this.workspaces;
const onChangeHandlers = this.handlers['onChangeWorkspaces'];
if (onChangeHandlers) {
onChangeHandlers.forEach(handler => {
handler(this.getWorkspaces());
});
}
return this.$q.resolve(this.getWorkspaces());
}, (error: any) => {
if (error && error.status === 304) {
return this.workspaces;
return this.$q.resolve(this.getWorkspaces());
}
return this.$q.reject(error);
});
let callbackPromises = updatedPromise.then((data: Array<che.IWorkspace>) => {
let promises = [];
promises.push(updatedPromise);
this.listeners.forEach((listener: any) => {
let promise = listener.onChangeWorkspaces(data);
promises.push(promise);
});
return this.$q.all(promises).then(() => data);
}, (error: any) => {
return this.$q.reject(error);
});
return callbackPromises;
return promise;
}
/**
@ -454,20 +474,20 @@ export class CheWorkspace {
* Performs workspace config update by the given workspaceId and new data.
* @param workspaceId {string} the workspace ID
* @param data {che.IWorkspace} the new workspace details
* @returns {ng.IPromise<any>}
*/
updateWorkspace(workspaceId: string, data: che.IWorkspace): ng.IPromise<any> {
let defer = this.$q.defer();
let promise = this.remoteWorkspaceAPI.updateWorkspace({workspaceId: workspaceId}, data).$promise;
promise.then((data: che.IWorkspace) => {
this.updateWorkspacesList(data);
this.startUpdateWorkspaceStatus(data.id);
defer.resolve(data);
}, (error: any) => {
defer.reject(error);
});
updateWorkspace(workspaceId: string, data: che.IWorkspace): ng.IPromise<che.IWorkspace> {
const promise = this.remoteWorkspaceAPI.updateWorkspace({workspaceId: workspaceId}, data).$promise;
return defer.promise;
return promise.then(data => {
this.updateWorkspacesList(data);
const onChangeHandlers = this.handlers['onChangeWorkspaces'];
if (onChangeHandlers) {
onChangeHandlers.forEach(handler => {
handler(this.getWorkspaces());
});
}
return this.$q.resolve(data);
});
}
/**
@ -479,9 +499,12 @@ export class CheWorkspace {
let defer = this.$q.defer();
let promise = this.remoteWorkspaceAPI.deleteWorkspace({workspaceId: workspaceId}).$promise;
promise.then(() => {
this.listeners.forEach((listener: any) => {
listener.onDeleteWorkspace(workspaceId);
});
const onDeleteHandlers = this.handlers['onDeleteWorkspace'];
if (onDeleteHandlers) {
onDeleteHandlers.forEach(handler => {
handler(workspaceId);
});
}
defer.resolve();
}, (error: any) => {
defer.reject(error);
@ -557,9 +580,9 @@ export class CheWorkspace {
if (this.subscribedWorkspacesIds.indexOf(workspaceId) < 0) {
this.subscribedWorkspacesIds.push(workspaceId);
this.cheJsonRpcMasterApi.subscribeWorkspaceStatus(workspaceId, (message: any) => {
let status = message.error ? 'ERROR' : message.status;
const status = message.error ? 'ERROR' : message.status;
if (this.workspaceStatuses.indexOf(status) >= 0) {
if (WorkspaceStatus[status]) {
this.getWorkspaceById(workspaceId).status = status;
}
@ -615,27 +638,15 @@ export class CheWorkspace {
this.workspacesById.set(workspace.id, workspace);
return;
}
const workspaceDetails = this.getWorkspaceById(workspace.id);
if (!workspaceDetails) {
this.workspacesById.set(workspace.id, workspace);
if (workspaceDetails && this.isWorkspaceRunning(workspaceDetails) && !workspace.runtime) {
workspace.runtime = workspaceDetails.runtime;
}
if (workspaceDetails && WorkspaceStatus[workspaceDetails.status] === WorkspaceStatus.RUNNING && workspaceDetails.runtime && !workspace.runtime) {
workspace.runtime = angular.copy(workspaceDetails.runtime);
}
this.lodash.remove(this.workspaces, (_workspace: che.IWorkspace) => {
return _workspace.id === workspace.id;
});
this.workspaces.push(workspace);
this.workspacesById.set(workspace.id, workspace);
// publish change
if (this.observables.has(workspace.id)) {
this.observables.get(workspace.id).publish(workspace);
}
if (!angular.equals(workspaceDetails, workspace)) {
this.fetchWorkspaceDetails(workspace.id);
return;
}
this.startUpdateWorkspaceStatus(workspace.id);
@ -649,6 +660,10 @@ export class CheWorkspace {
});
}
private isWorkspaceRunning(workspace: che.IWorkspace): boolean {
return workspace && WorkspaceStatus[workspace.status] === WorkspaceStatus.RUNNING;
}
private formJsonRpcApiLocation($location: ng.ILocationService, proxySettings: string, devmode: boolean): string {
let wsUrl;

View File

@ -86,15 +86,12 @@ describe('CheWorkspace', () => {
// add the listener
let listener = new Listener();
factory.addListener(listener);
factory.addListener('onChangeWorkspaces', listener.onChangeWorkspaces);
// no workspaces now on factory or on listener
expect(factory.getWorkspaces().length).toEqual(0);
expect(listener.getWorkspaces().length).toEqual(0);
// expecting a GET
httpBackend.expectGET('/api/workspace');
// providing request
// add workspaces on Http backend
cheBackend.addWorkspaces([workspace1, tmpWorkspace2]);

View File

@ -3,6 +3,8 @@
background-color $white-color
border-top 1px solid $list-separator-color
padding 0 10px
height 30px
max-height 30px
& > div
height 30px

View File

@ -38,9 +38,9 @@
cursor pointer
span:not(.tooltip)
max-width 100%
white-space nowrap
padding-right 3px
overflow visible
padding 0
& .che-xs-header
display inline-block
@ -81,10 +81,9 @@
& > *:not(.tooltip)
display inline-block
text-align center
float left
width 16px
height 16px
max-width 16px
max-height 16px
line-height 16px
margin-right 10px