Add basic user interface for application repos
Adds support for creating app repos, viewing app repos and seeing the list of app repos in the Quay UI.
This commit is contained in:
parent
3dd6e6919d
commit
f9e6110f73
47 changed files with 1009 additions and 106 deletions
|
@ -15,6 +15,10 @@ angular.module('quay').directive('repoPanelSettings', function () {
|
|||
controller: function($scope, $element, ApiService, Config) {
|
||||
$scope.deleteDialogCounter = 0;
|
||||
|
||||
var getTitle = function(repo) {
|
||||
return repo.kind == 'application' ? 'application' : 'image';
|
||||
};
|
||||
|
||||
$scope.getBadgeFormat = function(format, repository) {
|
||||
if (!repository) { return ''; }
|
||||
|
||||
|
@ -52,7 +56,8 @@ angular.module('quay').directive('repoPanelSettings', function () {
|
|||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not delete repository', callback);
|
||||
var errorHandler = ApiService.errorDisplay(
|
||||
'Could not delete ' + getTitle($scope.repository), callback);
|
||||
|
||||
ApiService.deleteRepository(null, params).then(function() {
|
||||
callback(true);
|
||||
|
@ -62,9 +67,11 @@ angular.module('quay').directive('repoPanelSettings', function () {
|
|||
}, errorHandler);
|
||||
};
|
||||
|
||||
|
||||
$scope.askChangeAccess = function(newAccess) {
|
||||
bootbox.confirm('Are you sure you want to make this repository ' + newAccess + '?', function(r) {
|
||||
var msg = 'Are you sure you want to make this ' + getTitle($scope.repository) + ' ' +
|
||||
newAccess + '?';
|
||||
|
||||
bootbox.confirm(msg, function(r) {
|
||||
if (!r) { return; }
|
||||
$scope.changeAccess(newAccess);
|
||||
});
|
||||
|
@ -81,7 +88,7 @@ angular.module('quay').directive('repoPanelSettings', function () {
|
|||
|
||||
ApiService.changeRepoVisibility(visibility, params).then(function() {
|
||||
$scope.repository.is_public = newAccess == 'public';
|
||||
}, ApiService.errorDisplay('Could not change repository visibility'));
|
||||
}, ApiService.errorDisplay('Could not change visibility'));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<div class="app-public-view-element">
|
||||
<div class="co-main-content-panel">
|
||||
<div class="app-row">
|
||||
<!-- Main panel -->
|
||||
<div class="col-md-9 main-content">
|
||||
<!-- App Header -->
|
||||
<div class="app-header">
|
||||
<a href="https://coreos.com/blog/quay-application-registry-for-kubernetes.html" class="hidden-xs hidden-sm" style="float: right; padding: 6px;" ng-safenewtab><i class="fa fa-info-circle" style="margin-right: 6px;"></i>Learn more about applications</a>
|
||||
<h3><i class="fa ci-appcube"></i>{{ $ctrl.repository.namespace }}/{{ $ctrl.repository.name }}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="co-top-tab-bar">
|
||||
<li class="co-top-tab" ng-class="$ctrl.currentTab == 'description' ? 'active': ''" ng-click="$ctrl.showTab('description')">
|
||||
Description
|
||||
</li>
|
||||
<li class="co-top-tab" ng-class="$ctrl.currentTab == 'channels' ? 'active': ''" ng-click="$ctrl.showTab('channels')">
|
||||
Channels
|
||||
</li>
|
||||
<li class="co-top-tab" ng-class="$ctrl.currentTab == 'releases' ? 'active': ''" ng-click="$ctrl.showTab('releases')">
|
||||
Releases
|
||||
</li>
|
||||
<li class="co-top-tab" ng-class="$ctrl.currentTab == 'settings' ? 'active': ''" ng-click="$ctrl.showTab('settings')"
|
||||
ng-if="$ctrl.repository.can_admin">
|
||||
Settings
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div ng-show="$ctrl.currentTab == 'description'">
|
||||
<div class="description markdown-input"
|
||||
content="$ctrl.repository.description"
|
||||
can-write="$ctrl.repository.can_write"
|
||||
content-changed="$ctrl.updateDescription"
|
||||
field-title="'application description'">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="$ctrl.currentTab == 'channels'">
|
||||
<div ng-show="!$ctrl.repository.channels.length && $ctrl.repository.can_write">
|
||||
<h3>No channels found for this application</h3>
|
||||
<br>
|
||||
<p>
|
||||
To push a new channel (from within the Helm package directory and with the <a href="https://coreos.com/apps" ng-safenewtab>Helm registry plugin</a> installed):
|
||||
<pre class="command">
|
||||
helm registry push --namespace {{ $ctrl.repository.namespace }} --channel {channelName} {{ $ctrl.Config.SERVER_HOSTNAME }}
|
||||
</pre>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div ng-show="$ctrl.repository.channels.length || !$ctrl.repository.can_write">
|
||||
<cor-table table-data="$ctrl.repository.channels" table-item-title="channels" filter-fields="['name']">
|
||||
<cor-table-col datafield="name" sortfield="name" title="Name"
|
||||
templateurl="/static/js/directives/ui/app-public-view/channel-name.html"></cor-table-col>
|
||||
<cor-table-col datafield="release" sortfield="release" title="Current Release"></cor-table-col>
|
||||
<cor-table-col datafield="last_modified" sortfield="last_modified" title="Last Modified"
|
||||
selected="true" dataKind="datetime"
|
||||
templateurl="/static/js/directives/ui/app-public-view/last-modified.html"></cor-table-col>
|
||||
</cor-table>
|
||||
</div>
|
||||
</div> <!-- /channels -->
|
||||
|
||||
<div ng-show="$ctrl.currentTab == 'releases'">
|
||||
<div ng-show="!$ctrl.repository.releases.length && $ctrl.repository.can_write">
|
||||
<h3>No releases found for this application</h3>
|
||||
<br>
|
||||
<p>
|
||||
To push a new release (from within the Helm package directory and with the <a href="https://coreos.com/apps" ng-safenewtab>Helm registry plugin</a> installed):
|
||||
<pre class="command">
|
||||
helm registry push --namespace {{ $ctrl.repository.namespace }} {{ $ctrl.Config.SERVER_HOSTNAME }}
|
||||
</pre>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div ng-show="$ctrl.repository.releases.length || !$ctrl.repository.can_write">
|
||||
<cor-table table-data="$ctrl.repository.releases" table-item-title="releases" filter-fields="['name']">
|
||||
<cor-table-col datafield="name" sortfield="name" title="Name"></cor-table-col>
|
||||
<cor-table-col datafield="last_modified" sortfield="last_modified"
|
||||
title="Created"
|
||||
selected="true" dataKind="datetime"
|
||||
templateurl="/static/js/directives/ui/app-public-view/last-modified.html"></cor-table-col>
|
||||
<cor-table-col datafield="channels" title="Channels"
|
||||
templateurl="/static/js/directives/ui/app-public-view/channels-list.html"></cor-table-col>
|
||||
</cor-table>
|
||||
</div>
|
||||
</div> <!-- /releases -->
|
||||
|
||||
<div ng-show="$ctrl.currentTab == 'settings'" ng-if="$ctrl.repository.can_admin">
|
||||
<div class="repo-panel-settings" repository="$ctrl.repository" is-enabled="$ctrl.settingsShown"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side bar -->
|
||||
<div class="col-md-3 side-bar">
|
||||
<div>
|
||||
<visibility-indicator repository="$ctrl.repository"></visibility-indicator>
|
||||
</div>
|
||||
<div ng-if="$ctrl.repository.is_public">{{ $ctrl.repository.namespace }} is sharing this application publicly</div>
|
||||
<div ng-if="!$ctrl.repository.is_public">This application is private and only visible to those with permission</div>
|
||||
|
||||
<div class="sidebar-table" ng-if="$ctrl.repository.channels.length">
|
||||
<h4>Latest Channels</h4>
|
||||
<cor-table table-data="$ctrl.repository.channels" table-item-title="channels" filter-fields="['name']" compact="true" max-display-count="3">
|
||||
<cor-table-col datafield="name" sortfield="name" title="Name"
|
||||
templateurl="/static/js/directives/ui/app-public-view/channel-name.html"></cor-table-col>
|
||||
<cor-table-col datafield="release" sortfield="release" title="Current Release"></cor-table-col>
|
||||
</cor-table>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-table" ng-if="$ctrl.repository.releases.length">
|
||||
<h4>Latest Releases</h4>
|
||||
<cor-table table-data="$ctrl.repository.releases" table-item-title="releases" filter-fields="['name']" compact="true" max-display-count="3">
|
||||
<cor-table-col datafield="name" sortfield="name" title="Name"></cor-table-col>
|
||||
<cor-table-col datafield="last_modified" sortfield="last_modified"
|
||||
title="Created"
|
||||
selected="true" dataKind="datetime"
|
||||
templateurl="/static/js/directives/ui/app-public-view/last-modified.html"></cor-table-col>
|
||||
</cor-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,30 @@
|
|||
import { Input, Component } from 'angular-ts-decorators';
|
||||
|
||||
/**
|
||||
* A component that displays the public information associated with an application repository.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'appPublicView',
|
||||
templateUrl: '/static/js/directives/ui/app-public-view/app-public-view.component.html'
|
||||
})
|
||||
export class AppPublicViewComponent implements ng.IComponentController {
|
||||
@Input('<') public repository: any;
|
||||
private currentTab: string = 'description';
|
||||
private settingsShown: number = 0;
|
||||
|
||||
constructor(private Config: any) {
|
||||
this.updateDescription = this.updateDescription.bind(this);
|
||||
}
|
||||
|
||||
private updateDescription(content: string) {
|
||||
this.repository.description = content;
|
||||
this.repository.put();
|
||||
}
|
||||
|
||||
public showTab(tab: string): void {
|
||||
this.currentTab = tab;
|
||||
if (tab == 'settings') {
|
||||
this.settingsShown++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<channel-icon name="item.name"></channel-icon><span style="vertical-align: middle; margin-left: 6px;">{{ item.name }}</span>
|
|
@ -0,0 +1,3 @@
|
|||
<span ng-repeat="channel_name in item.channels">
|
||||
<channel-icon name="channel_name"></channel-icon>
|
||||
</span>
|
|
@ -0,0 +1,3 @@
|
|||
<span data-title="{{ item.last_modified | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}" bs-tooltip>
|
||||
<span am-time-ago="item.last_modified"></span>
|
||||
</span>
|
|
@ -0,0 +1,7 @@
|
|||
<span class="channel-icon-element" data-title="{{ $ctrl.name }}" bs-tooltip>
|
||||
<span class="hexagon" ng-style="{'background-color': $ctrl.color($ctrl.name)}">
|
||||
<span class="before" ng-style="{'border-bottom-color': $ctrl.color($ctrl.name)}"></span>
|
||||
<span class="after" ng-style="{'border-top-color': $ctrl.color($ctrl.name)}"></span>
|
||||
</span>
|
||||
<b>{{ $ctrl.initial($ctrl.name) }}</b>
|
||||
</span>
|
|
@ -0,0 +1,47 @@
|
|||
import { Input, Component } from 'angular-ts-decorators';
|
||||
|
||||
/**
|
||||
* A component that displays the icon of a channel.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'channelIcon',
|
||||
templateUrl: '/static/js/directives/ui/channel-icon/channel-icon.component.html',
|
||||
})
|
||||
export class ChannelIconComponent implements ng.IComponentController {
|
||||
@Input('<') public name: string;
|
||||
|
||||
private colors: any;
|
||||
|
||||
constructor(Config: any, private md5: any) {
|
||||
this.colors = Config['CHANNEL_COLORS'];
|
||||
}
|
||||
|
||||
private initial(name: string): string {
|
||||
if (name == 'alpha') {
|
||||
return 'α';
|
||||
}
|
||||
if (name == 'beta') {
|
||||
return 'β';
|
||||
}
|
||||
if (name == 'stable') {
|
||||
return 'S';
|
||||
}
|
||||
return name[0].toUpperCase();
|
||||
}
|
||||
|
||||
private color(name: string): string {
|
||||
if (name == 'alpha') {
|
||||
return this.colors[0];
|
||||
}
|
||||
if (name == 'beta') {
|
||||
return this.colors[1];
|
||||
}
|
||||
if (name == 'stable') {
|
||||
return this.colors[2];
|
||||
}
|
||||
|
||||
var hash: string = this.md5.createHash(name);
|
||||
var num: number = parseInt(hash.substr(0, 4));
|
||||
return this.colors[num % this.colors.length];
|
||||
}
|
||||
}
|
40
static/js/directives/ui/cor-table/cor-table-col.component.ts
Normal file
40
static/js/directives/ui/cor-table/cor-table-col.component.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Input, Component } from 'angular-ts-decorators';
|
||||
import { CorTableComponent } from './cor-table.component';
|
||||
|
||||
/**
|
||||
* Defines a column (optionally sortable) in the table.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'corTableCol',
|
||||
template: '',
|
||||
require: {
|
||||
parent: '^^corTable'
|
||||
},
|
||||
})
|
||||
export class CorTableColumn implements ng.IComponentController {
|
||||
@Input('@') public title: string;
|
||||
@Input('@') public templateurl: string;
|
||||
|
||||
@Input('@') public datafield: string;
|
||||
@Input('@') public sortfield: string;
|
||||
@Input('@') public selected: string;
|
||||
@Input('@') public dataKind: string;
|
||||
|
||||
private parent: CorTableComponent;
|
||||
|
||||
public $onInit(): void {
|
||||
this.parent.addColumn(this);
|
||||
}
|
||||
|
||||
public isNumeric(): boolean {
|
||||
return this.dataKind == 'datetime';
|
||||
}
|
||||
|
||||
public processColumnForOrdered(tableService: any, value: any): any {
|
||||
if (this.dataKind == 'datetime') {
|
||||
return tableService.getReversedTimestamp(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
42
static/js/directives/ui/cor-table/cor-table.component.html
Normal file
42
static/js/directives/ui/cor-table/cor-table.component.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<div class="cor-table-element">
|
||||
<span ng-transclude/>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="co-top-bar" ng-if="$ctrl.compact != 'true'">
|
||||
<span class="co-filter-box with-options" ng-if="$ctrl.tableData.length && $ctrl.filterFields.length">
|
||||
<span class="filter-message" ng-if="$ctrl.options.filter">
|
||||
Showing {{ $ctrl.orderedData.entries.length }} of {{ $ctrl.tableData.length }} {{ $ctrl.tableItemTitle }}
|
||||
</span>
|
||||
<input class="form-control" type="text" ng-model="$ctrl.options.filter"
|
||||
placeholder="Filter {{ $ctrl.tableItemTitle }}..." ng-change="$ctrl.refreshOrder()">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div class="empty" ng-if="!$ctrl.tableData.length && $ctrl.compact != 'true'">
|
||||
<div class="empty-primary-msg">No {{ $ctrl.tableItemTitle }} found.</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<table class="co-table" ng-show="$ctrl.tableData.length">
|
||||
<thead>
|
||||
<td ng-repeat="col in $ctrl.columns" ng-class="$ctrl.tablePredicateClass(col, $ctrl.options)">
|
||||
<a ng-click="$ctrl.setOrder(col)">{{ col.title }}</a>
|
||||
</td>
|
||||
</thead>
|
||||
<tbody ng-repeat="item in $ctrl.orderedData.visibleEntries | limitTo:$ctrl.maxDisplayCount">
|
||||
<tr>
|
||||
<td ng-repeat="col in $ctrl.columns">
|
||||
<div ng-include="col.templateurl" ng-if="col.templateurl"></div>
|
||||
<div ng-if="!col.templateurl">{{ item[col.datafield] }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="empty" ng-if="!$ctrl.orderedData.entries.length && $ctrl.tableData.length"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching {{ $ctrl.tableItemTitle }} found.</div>
|
||||
<div class="empty-secondary-msg">Try adjusting your filter above.</div>
|
||||
</div>
|
||||
</div>
|
82
static/js/directives/ui/cor-table/cor-table.component.ts
Normal file
82
static/js/directives/ui/cor-table/cor-table.component.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { Input, Component } from 'angular-ts-decorators';
|
||||
import { CorTableColumn } from './cor-table-col.component';
|
||||
|
||||
/**
|
||||
* A component that displays a table of information, with optional filtering and automatic sorting.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'corTable',
|
||||
templateUrl: '/static/js/directives/ui/cor-table/cor-table.component.html',
|
||||
transclude: true,
|
||||
})
|
||||
export class CorTableComponent implements ng.IComponentController {
|
||||
@Input('=') public tableData: any[];
|
||||
@Input('@') public tableItemTitle: string;
|
||||
@Input('<') public filterFields: string[];
|
||||
@Input('@') public compact: string;
|
||||
@Input('<') public maxDisplayCount: number;
|
||||
|
||||
private columns: CorTableColumn[];
|
||||
private orderedData: any;
|
||||
private options: any;
|
||||
|
||||
constructor(private TableService: any) {
|
||||
this.columns = [];
|
||||
this.options = {
|
||||
'filter': '',
|
||||
'reverse': false,
|
||||
'predicate': '',
|
||||
'page': 0,
|
||||
};
|
||||
}
|
||||
|
||||
public addColumn(col: CorTableColumn): void {
|
||||
this.columns.push(col);
|
||||
|
||||
if (col.selected == 'true') {
|
||||
this.options['predicate'] = col.datafield;
|
||||
}
|
||||
|
||||
this.refreshOrder();
|
||||
}
|
||||
|
||||
private setOrder(col: CorTableColumn): void {
|
||||
this.TableService.orderBy(col.datafield, this.options);
|
||||
this.refreshOrder();
|
||||
}
|
||||
|
||||
private tablePredicateClass(col: CorTableColumn, options: any) {
|
||||
return this.TableService.tablePredicateClass(col.datafield, this.options.predicate,
|
||||
this.options.reverse);
|
||||
}
|
||||
|
||||
private refreshOrder(): void {
|
||||
var columnMap = {};
|
||||
this.columns.forEach(function(col) {
|
||||
columnMap[col.datafield] = col;
|
||||
});
|
||||
|
||||
var filterCols = this.columns.filter(function(col) {
|
||||
return !!col.sortfield;
|
||||
}).map((col) => (col.datafield));
|
||||
|
||||
var numericCols = this.columns.filter(function(col) {
|
||||
return col.isNumeric();
|
||||
}).map((col) => (col.datafield));
|
||||
|
||||
var processed = this.tableData.map((item) => {
|
||||
var keys = Object.keys(item);
|
||||
var newObj = {};
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var key = keys[i];
|
||||
if (columnMap[key]) {
|
||||
newObj[key] = columnMap[key].processColumnForOrdered(this.TableService, item[key]);
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
});
|
||||
|
||||
this.orderedData = this.TableService.buildOrderedItems(processed, this.options,
|
||||
filterCols, numericCols);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,8 @@ angular.module('quay').directive('repoListGrid', function () {
|
|||
namespace: '=namespace',
|
||||
starToggled: '&starToggled',
|
||||
hideTitle: '=hideTitle',
|
||||
hideNamespaces: '=hideNamespaces'
|
||||
hideNamespaces: '=hideNamespaces',
|
||||
repoKind: '@repoKind'
|
||||
},
|
||||
controller: function($scope, $element, UserService) {
|
||||
$scope.isOrganization = function(namespace) {
|
||||
|
|
|
@ -11,7 +11,8 @@ angular.module('quay').directive('repoListTable', function () {
|
|||
scope: {
|
||||
'repositoriesResources': '=repositoriesResources',
|
||||
'namespaces': '=namespaces',
|
||||
'starToggled': '&starToggled'
|
||||
'starToggled': '&starToggled',
|
||||
'repoKind': '@repoKind'
|
||||
},
|
||||
controller: function($scope, $element, $filter, TableService, UserService) {
|
||||
$scope.repositories = null;
|
||||
|
|
|
@ -12,6 +12,7 @@ angular.module('quay').directive('repoListView', function () {
|
|||
namespaces: '=namespaces',
|
||||
starredRepositories: '=starredRepositories',
|
||||
starToggled: '&starToggled',
|
||||
repoKind: '@repoKind'
|
||||
},
|
||||
controller: function($scope, $element, CookieService) {
|
||||
$scope.resources = [];
|
||||
|
|
18
static/js/directives/ui/repository-title.js
Normal file
18
static/js/directives/ui/repository-title.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* An element which displays the title of a repository (either 'repository' or 'application').
|
||||
*/
|
||||
angular.module('quay').directive('repositoryTitle', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repository-title.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '<repository'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
<span class="visibility-indicator-component-element" ng-class="{'public': $ctrl.repository.is_public, 'private': !$ctrl.repository.is_public}">
|
||||
<span class="public" ng-if="$ctrl.repository.is_public">Public</span>
|
||||
<span class="private" ng-if="!$ctrl.repository.is_public">Private</span>
|
||||
</span>
|
|
@ -0,0 +1,17 @@
|
|||
import { Input, Component } from 'angular-ts-decorators';
|
||||
|
||||
/**
|
||||
* A component that displays a box with "Public" or "Private", depending on the visibility
|
||||
* of the repository.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'visibilityIndicator',
|
||||
templateUrl: '/static/js/directives/ui/visibility-indicator/visibility-indicator.component.html'
|
||||
})
|
||||
export class VisibilityIndicatorComponent implements ng.IComponentController {
|
||||
@Input('<') public repository: any;
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
82
static/js/pages/app-list.js
Normal file
82
static/js/pages/app-list.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
(function() {
|
||||
/**
|
||||
* Application listing page. Shows all applications for all visibile namespaces.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('app-list', 'app-list.html', AppListCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Applications',
|
||||
'description': 'View and manage applications'
|
||||
})
|
||||
}]);
|
||||
|
||||
function AppListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService, Features) {
|
||||
$scope.namespace = null;
|
||||
$scope.page = 1;
|
||||
$scope.publicPageCount = null;
|
||||
$scope.allRepositories = {};
|
||||
$scope.loading = true;
|
||||
$scope.resources = [];
|
||||
$scope.Features = Features;
|
||||
|
||||
// When loading the UserService, if the user is logged in, create a list of
|
||||
// relevant namespaces and collect the relevant repositories.
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
$scope.loading = false;
|
||||
if (!user.anonymous) {
|
||||
// Add our user to our list of namespaces.
|
||||
$scope.namespaces = [{
|
||||
'name': user.username,
|
||||
'avatar': user.avatar
|
||||
}];
|
||||
|
||||
// Add each org to our list of namespaces.
|
||||
user.organizations.map(function(org) {
|
||||
$scope.namespaces.push({
|
||||
'name': org.name,
|
||||
'avatar': org.avatar
|
||||
});
|
||||
});
|
||||
|
||||
// Load the repos.
|
||||
loadRepos();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.isOrganization = function(namespace) {
|
||||
return !!UserService.getOrganization(namespace);
|
||||
};
|
||||
|
||||
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.
|
||||
var findDuplicateRepo = function(repo) {
|
||||
var found = $scope.allRepositories[repo.namespace + '/' + repo.name];
|
||||
if (found) {
|
||||
return found;
|
||||
} else {
|
||||
$scope.allRepositories[repo.namespace + '/' + repo.name] = repo;
|
||||
return repo;
|
||||
}
|
||||
};
|
||||
|
||||
var loadRepos = function() {
|
||||
if (!$scope.user || $scope.user.anonymous || $scope.namespaces.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.namespaces.map(function(namespace) {
|
||||
var options = {
|
||||
'namespace': namespace.name,
|
||||
'last_modified': true,
|
||||
'popularity': true,
|
||||
'repo_kind': 'application'
|
||||
};
|
||||
|
||||
namespace.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories.map(findDuplicateRepo);
|
||||
});
|
||||
|
||||
$scope.resources.push(namespace.repositories);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
39
static/js/pages/app-view.js
Normal file
39
static/js/pages/app-view.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
(function() {
|
||||
/**
|
||||
* Application view page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('app-view', 'app-view.html', AppViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': '{{ namespace }}/{{ name }}',
|
||||
'description': 'Application {{ namespace }}/{{ name }}'
|
||||
});
|
||||
}]);
|
||||
|
||||
function AppViewCtrl($scope, $routeParams, $location, $timeout, ApiService, UserService, AngularPollChannel, ImageLoaderService, CookieService) {
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
|
||||
$scope.viewScope = {};
|
||||
$scope.settingsShown = 0;
|
||||
|
||||
$scope.showSettings = function() {
|
||||
$scope.settingsShown++;
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'repo_kind': 'application',
|
||||
'includeStats': true
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repository = repo;
|
||||
$scope.viewScope.repository = repo;
|
||||
});
|
||||
};
|
||||
|
||||
loadRepository();
|
||||
}
|
||||
})();
|
|
@ -20,7 +20,8 @@
|
|||
'is_public': 0,
|
||||
'description': '',
|
||||
'initialize': '',
|
||||
'name': $routeParams['name']
|
||||
'name': $routeParams['name'],
|
||||
'repo_kind': 'image'
|
||||
};
|
||||
|
||||
$scope.changeNamespace = function(namespace) {
|
||||
|
@ -54,13 +55,19 @@
|
|||
'namespace': repo.namespace,
|
||||
'repository': repo.name,
|
||||
'visibility': repo.is_public == '1' ? 'public' : 'private',
|
||||
'description': repo.description
|
||||
'description': repo.description,
|
||||
'repo_kind': repo.repo_kind
|
||||
};
|
||||
|
||||
ApiService.createRepo(data).then(function(created) {
|
||||
$scope.creating = false;
|
||||
$scope.created = created;
|
||||
|
||||
if (repo.repo_kind == 'application') {
|
||||
$location.path('/application/' + created.namespace + '/' + created.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the build if applicable.
|
||||
if ($scope.repo.initialize == 'dockerfile' || $scope.repo.initialize == 'zipfile') {
|
||||
$scope.createdForBuild = created;
|
||||
|
|
|
@ -40,6 +40,12 @@ export class QuayRoutes {
|
|||
}
|
||||
|
||||
routeBuilder
|
||||
// Application View
|
||||
.route('/application/:namespace/:name', 'app-view')
|
||||
|
||||
// Repo List
|
||||
.route('/application/', 'app-list')
|
||||
|
||||
// Repository View
|
||||
.route('/repository/:namespace/:name', 'repo-view')
|
||||
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
|
||||
|
|
|
@ -12,6 +12,11 @@ import { ManageTriggerCustomGitComponent } from './directives/ui/manage-trigger-
|
|||
import { ManageTriggerGithostComponent } from './directives/ui/manage-trigger-githost/manage-trigger-githost.component';
|
||||
import { LinearWorkflowComponent } from './directives/ui/linear-workflow/linear-workflow.component';
|
||||
import { LinearWorkflowSectionComponent } from './directives/ui/linear-workflow/linear-workflow-section.component';
|
||||
import { AppPublicViewComponent } from './directives/ui/app-public-view/app-public-view.component';
|
||||
import { VisibilityIndicatorComponent } from './directives/ui/visibility-indicator/visibility-indicator.component';
|
||||
import { CorTableComponent } from './directives/ui/cor-table/cor-table.component';
|
||||
import { CorTableColumn } from './directives/ui/cor-table/cor-table-col.component';
|
||||
import { ChannelIconComponent } from './directives/ui/channel-icon/channel-icon.component';
|
||||
import { QuayConfig } from './quay-config.module';
|
||||
import { QuayRun } from './quay-run.module';
|
||||
import { BuildServiceImpl } from './services/build/build.service.impl';
|
||||
|
@ -37,6 +42,11 @@ import { DataFileServiceImpl } from './services/datafile/datafile.service.impl';
|
|||
ManageTriggerGithostComponent,
|
||||
LinearWorkflowComponent,
|
||||
LinearWorkflowSectionComponent,
|
||||
AppPublicViewComponent,
|
||||
VisibilityIndicatorComponent,
|
||||
CorTableComponent,
|
||||
CorTableColumn,
|
||||
ChannelIconComponent,
|
||||
],
|
||||
providers: [
|
||||
ViewArrayImpl,
|
||||
|
|
Reference in a new issue