diff --git a/static/css/core-ui.css b/static/css/core-ui.css
index 1c4672081..ef80ab7d7 100644
--- a/static/css/core-ui.css
+++ b/static/css/core-ui.css
@@ -162,161 +162,6 @@ a:focus {
}
}
-.co-tab-container {
- padding: 0px;
-}
-
-.co-tabs {
- margin: 0px;
- padding: 0px;
- width: 82px;
- display: table-cell;
- vertical-align: top;
-}
-
-.co-tab-element {
- display: table-cell;
- float: none;
- vertical-align: top;
- background-color: #e8f1f6;
- border-right: 1px solid #DDE7ED;
-}
-
-.co-tab-content {
- width: 100%;
- display: table-cell;
- float: none;
- padding: 30px;
-}
-
-@media (max-width: 767px) {
- .co-tab-content {
- padding: 20px;
- width: 100%;
- display: block;
- }
-}
-
-.co-tabs li {
- list-style: none;
- display: block;
- border-bottom: 1px solid #DDE7ED;
-}
-
-.co-tabs li.active {
- background-color: white;
- border-right: 1px solid white;
- margin-right: -1px;
-}
-
-.co-tabs li a {
- display: block;
- width: 82px;
- height: 82px;
- line-height: 82px;
- text-align: center;
- font-size: 36px;
-}
-
-.co-tabs li a i {
- font-size: 36px;
- color: gray;
-}
-
-.co-tabs li.active a {
- color: black;
-}
-
-.co-tabs .xs-toggle {
- display: none;
-}
-
-@media (max-width: 767px) {
- .co-tabs {
- display: block;
- width: auto;
- border-right: none;
- border-bottom: 1px solid #DDE7ED;
- }
-
- .co-tab-element {
- position: relative;
- display: block;
- }
-
- .co-tab-element .xs-toggle {
- display: inline-block;
- position: absolute;
- top: 10px;
- right: 10px;
- z-index: 2;
- width: 40px;
- height: 40px;
- cursor: pointer;
- font-size: 18px;
- text-align: center;
- line-height: 40px;
- border: 2px solid #DDE7ED;
- background: white;
- }
-
- .co-tab-element.closed .xs-toggle:before {
- content: "\f0d7";
- font-family: FontAwesome;
- }
-
- .co-tab-element.open .xs-toggle:before {
- content: "\f0d8";
- font-family: FontAwesome;
- }
-
- .co-tab-element .xs-label {
- line-height: 60px;
- font-size: 16px;
- margin-left: 16px;
- display: inline-block !important;
- color: gray;
- }
-
- .co-tabs li a {
- display: inline-block;
- height: 60px;
- line-height: 60px;
- white-space: nowrap;
- width: 100%;
- text-align: left;
- padding-left: 20px;
- text-decoration: none !important;
- font-size: 28px;
- }
-
- .co-tabs li a i {
- vertical-align: middle;
- font-size: 28px;
- }
-
- .co-tabs li.active a .xs-label {
- color: black;
- }
-
- .co-tabs li.active a i {
- color: black;
- }
-
- .co-tab-element.open li {
- height: 60px;
- }
-
- .co-tab-element.closed li {
- height: 0px;
- overflow: hidden;
- }
-
- .co-tab-element.closed li.active {
- height: 60px;
- }
-}
-
.co-main-content-panel {
margin-bottom: 20px;
background-color: #fff;
@@ -330,11 +175,6 @@ a:focus {
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
}
-.co-tab-panel {
- padding: 0px;
-}
-
-
.cor-log-box {
width: 100%;
height: 550px;
@@ -1647,98 +1487,6 @@ a:focus {
color: #aaa;
}
-.co-dialog .co-tabs {
- width: auto;
- min-height: 400px;
- padding-top: 58px;
-}
-
-.co-dialog .co-tabs li a {
- width: auto;
- height: 42px;
- line-height: 42px;
- text-align: left;
- font-size: 120%;
- white-space: nowrap;
- padding: 0px;
- padding-left: 16px;
- padding-right: 30px;
- color: #5A5A5A;
-}
-
-.co-dialog .co-tabs li.active a, .co-dialog .co-tabs li.active a .fa {
- color: black;
-
- filter: none !important;
- -webkit-filter: none !important;
-}
-
-.co-dialog .co-tabs li a .fa {
- vertical-align: middle;
- margin-right: 10px;
- font-size: 20px;
- line-height: 20px;
- width: 20px;
- text-align: center;
- margin-top: -2px;
-}
-
-.co-dialog .co-tabs li a .fa.icon {
- width: 20px;
- height: 20px;
- background-size: 20px;
- filter: grayscale(100%);
- -webkit-filter: grayscale(100%);
-}
-
-.co-dialog .co-tab-content {
- padding: 16px;
- padding-bottom: 30px;
-}
-
-.co-dialog .co-tab-content h3 {
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-.co-dialog .co-tab-content label {
- margin-top: 24px;
- font-weight: bold;
- font-size: 16px;
-}
-
-.co-dialog .co-tab-content .help-text {
- margin-top: 7px;
- margin-left: 6px;
- color: #aaa;
-}
-
-.co-dialog .co-tab-content .co-list-table {
- width: 100%;
-}
-
-.co-dialog .co-tab-content .co-list-table td:first-child {
- white-space: nowrap;
- vertical-align: middle;
- font-weight: normal;
-}
-
-.co-dialog .co-tabs li:first-child {
- border-top: 1px solid #DDE7ED;
-}
-
-@media screen and (max-width: 767px) {
- .co-dialog .co-tabs {
- min-height: 0px;
- padding-top: 0px;
- }
-
- .co-dialog .co-tabs li a {
- line-height: 60px;
- height: auto;
- }
-}
-
.co-modal-body-scrollable {
overflow-y: auto;
overflow-x: hidden;
diff --git a/static/css/directives/ui/cor-tabs.css b/static/css/directives/ui/cor-tabs.css
new file mode 100644
index 000000000..e7f405d9c
--- /dev/null
+++ b/static/css/directives/ui/cor-tabs.css
@@ -0,0 +1,254 @@
+cor-tabs {
+ display: table-cell;
+ float: none;
+ vertical-align: top;
+ background-color: #e8f1f6;
+ border-right: 1px solid #DDE7ED;
+}
+
+.co-tab-container {
+ padding: 0px;
+}
+
+.co-tabs {
+ margin: 0px;
+ padding: 0px;
+ width: 82px;
+ display: table-cell;
+ vertical-align: top;
+}
+
+.co-tab-content {
+ width: 100%;
+ display: table-cell;
+ float: none;
+ padding: 30px;
+}
+
+@media (max-width: 767px) {
+ .co-tab-content {
+ padding: 20px;
+ width: 100%;
+ display: block;
+ }
+}
+
+.co-tabs li {
+ list-style: none;
+ display: block;
+ border-bottom: 1px solid #DDE7ED;
+}
+
+.co-tabs li.active {
+ background-color: white;
+ border-right: 1px solid white;
+ margin-right: -1px;
+}
+
+.co-tabs li a {
+ display: block;
+ width: 82px;
+ height: 82px;
+ line-height: 82px;
+ text-align: center;
+ font-size: 36px;
+}
+
+.co-tabs li a i {
+ font-size: 36px;
+ color: gray;
+}
+
+.co-tabs li.active a {
+ color: black;
+}
+
+.co-tabs .xs-toggle {
+ display: none;
+}
+
+@media (max-width: 767px) {
+ .co-tabs {
+ display: block;
+ width: auto;
+ border-right: none;
+ border-bottom: 1px solid #DDE7ED;
+ }
+
+ cor-tabs {
+ position: relative;
+ display: block;
+ }
+
+ .co-tab-element .xs-toggle {
+ display: inline-block;
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 2;
+ width: 40px;
+ height: 40px;
+ cursor: pointer;
+ font-size: 18px;
+ text-align: center;
+ line-height: 40px;
+ border: 2px solid #DDE7ED;
+ background: white;
+ }
+
+ .co-tab-element.closed .xs-toggle:before {
+ content: "\f0d7";
+ font-family: FontAwesome;
+ }
+
+ .co-tab-element.open .xs-toggle:before {
+ content: "\f0d8";
+ font-family: FontAwesome;
+ }
+
+ .co-tab-element .xs-label {
+ line-height: 60px;
+ font-size: 16px;
+ margin-left: 16px;
+ display: inline-block !important;
+ color: gray;
+ }
+
+ .co-tabs li a {
+ display: inline-block;
+ height: 60px;
+ line-height: 60px;
+ white-space: nowrap;
+ width: 100%;
+ text-align: left;
+ padding-left: 20px;
+ text-decoration: none !important;
+ font-size: 28px;
+ }
+
+ .co-tabs li a i {
+ vertical-align: middle;
+ font-size: 28px;
+ }
+
+ .co-tabs li.active a .xs-label {
+ color: black;
+ }
+
+ .co-tabs li.active a i {
+ color: black;
+ }
+
+ .co-tab-element.open li {
+ height: 60px;
+ }
+
+ .co-tab-element.closed li {
+ height: 0px;
+ overflow: hidden;
+ }
+
+ .co-tab-element.closed li.active {
+ height: 60px;
+ }
+}
+
+.co-tab-panel {
+ padding: 0px;
+}
+
+.co-dialog .co-tabs {
+ width: auto;
+ min-height: 400px;
+ padding-top: 58px;
+}
+
+.co-dialog .co-main-content-panel {
+ margin-bottom: 0px;
+}
+
+.co-dialog .co-tabs li a {
+ width: auto;
+ height: 42px;
+ line-height: 42px;
+ text-align: left;
+ font-size: 120%;
+ white-space: nowrap;
+ padding: 0px;
+ padding-left: 16px;
+ padding-right: 30px;
+ color: #5A5A5A;
+}
+
+.co-dialog .co-tabs li.active a, .co-dialog .co-tabs li.active a .fa {
+ color: black;
+
+ filter: none !important;
+ -webkit-filter: none !important;
+}
+
+.co-dialog .co-tabs li a .fa {
+ vertical-align: middle;
+ margin-right: 10px;
+ font-size: 20px;
+ line-height: 20px;
+ width: 20px;
+ text-align: center;
+ margin-top: -2px;
+}
+
+.co-dialog .co-tabs li a .fa.icon {
+ width: 20px;
+ height: 20px;
+ background-size: 20px;
+ filter: grayscale(100%);
+ -webkit-filter: grayscale(100%);
+}
+
+.co-dialog .co-tab-content {
+ padding: 16px;
+ padding-bottom: 30px;
+}
+
+.co-dialog .co-tab-content h3 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.co-dialog .co-tab-content label {
+ margin-top: 24px;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.co-dialog .co-tab-content .help-text {
+ margin-top: 7px;
+ margin-left: 6px;
+ color: #aaa;
+}
+
+.co-dialog .co-tab-content .co-list-table {
+ width: 100%;
+}
+
+.co-dialog .co-tab-content .co-list-table td:first-child {
+ white-space: nowrap;
+ vertical-align: middle;
+ font-weight: normal;
+}
+
+.co-dialog .co-tabs li:first-child {
+ border-top: 1px solid #DDE7ED;
+}
+
+@media screen and (max-width: 767px) {
+ .co-dialog .co-tabs {
+ min-height: 0px;
+ padding-top: 0px;
+ }
+
+ .co-dialog .co-tabs li a {
+ line-height: 60px;
+ height: auto;
+ }
+}
\ No newline at end of file
diff --git a/static/directives/cor-tab.html b/static/directives/cor-tab.html
deleted file mode 100644
index a0ba352da..000000000
--- a/static/directives/cor-tab.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- {{ tabTitle }}
-
-
-
diff --git a/static/directives/cor-tabs.html b/static/directives/cor-tabs.html
deleted file mode 100644
index 7903bb8a4..000000000
--- a/static/directives/cor-tabs.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/static/directives/credentials-dialog.html b/static/directives/credentials-dialog.html
index ea082318b..b2bef307d 100644
--- a/static/directives/credentials-dialog.html
+++ b/static/directives/credentials-dialog.html
@@ -6,42 +6,42 @@
-
+
-
-
+
+
{{ secretTitle }}
-
+
-
+
Kubernetes Secret
-
+
-
+
rkt Configuration
-
+
-
+
Docker Login
-
+
-
+
Docker Configuration
-
+
-
+
Mesos Credentials
-
-
+
+
-
+
Credentials for {{ credentials.username }}
×
-
+
{{ secretTitle }}:
@@ -49,9 +49,9 @@
-
+
-
+
Step 1: Download secret
First, download the Kubernetes pull secret for the {{ entityTitle }}:
@@ -83,9 +83,9 @@ spec:
imagePullSecrets:
- name: {{ getKubernetesSecretName(credentials) }}
-
+
-
+
Step 1: Download credentials bundle
First, download the Docker credentials file as a bundle:
@@ -122,9 +122,9 @@ spec:
]
}
-
+
-
+
Step 1: Download credentials config
First, download the rkt credentials file for the {{ entityTitle }}:
@@ -137,15 +137,15 @@ spec:
Step 2: Write to disk
Second, place the file in the rkt configuration directory:
-
+
-
+
Run docker login
Enter the following command on the command line:
-
+
-
+
Step 1: Download credentials config
First, download the Docker credentials file for the {{ entityTitle }}:
@@ -158,9 +158,9 @@ spec:
Step 2: Write to disk
Second, place the file in the Docker configuration directory. Note: This will overwrite existing credentials:
-
-
-
+
+
+
diff --git a/static/js/core-ui.js b/static/js/core-ui.js
index 94728c83b..3d94d659c 100644
--- a/static/js/core-ui.js
+++ b/static/js/core-ui.js
@@ -214,61 +214,6 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
- .directive('corTabPanel', function() {
- var directiveDefinitionObject = {
- priority: 1,
- templateUrl: '/static/directives/cor-tab-panel.html',
- replace: true,
- transclude: true,
- restrict: 'C',
- scope: {},
- controller: function($rootScope, $scope, $element) {
- }
- };
- return directiveDefinitionObject;
- })
-
- .directive('corTabContent', function() {
- var directiveDefinitionObject = {
- priority: 2,
- replace: true,
- transclude: false,
- restrict: 'C',
- scope: {},
- controller: function($rootScope, $scope, $element) {
- $element.addClass('co-tab-content tab-content col-md-11');
- }
- };
- return directiveDefinitionObject;
- })
-
- .directive('corTabs', function() {
- var directiveDefinitionObject = {
- priority: 3,
- templateUrl: '/static/directives/cor-tabs.html',
- replace: true,
- transclude: true,
- restrict: 'C',
- scope: {
- 'rememberCookie': '@rememberCookie'
- },
- controller: function($rootScope, $scope, $element, $timeout, $location, UIService) {
- $scope.isClosed = true;
-
- $scope.toggleClosed = function(e) {
- $scope.isClosed = !$scope.isClosed;
- e.stopPropagation();
- e.preventDefault();
- };
-
- UIService.initializeTabs($scope, $element, function() {
- $scope.isClosed = true;
- }, $scope.rememberCookie);
- }
- };
- return directiveDefinitionObject;
- })
-
.directive('corFloatingBottomBar', function() {
var directiveDefinitionObject = {
priority: 3,
@@ -338,34 +283,6 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
- .directive('corTab', function() {
- var directiveDefinitionObject = {
- priority: 4,
- templateUrl: '/static/directives/cor-tab.html',
- replace: true,
- transclude: true,
- restrict: 'C',
- scope: {
- 'tabActive': '@tabActive',
- 'tabTitle': '@tabTitle',
- 'tabTarget': '@tabTarget',
- 'tabInit': '&tabInit',
- 'tabShown': '&tabShown',
- 'tabHidden': '&tabHidden'
- },
- controller: function($rootScope, $scope, $element) {
- $element.find('a[data-toggle="tab"]').on('hidden.bs.tab', function (e) {
- $scope.tabHidden({});
- });
-
- $element.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
- $scope.tabShown({});
- });
- }
- };
- return directiveDefinitionObject;
- })
-
.directive('corStep', function() {
var directiveDefinitionObject = {
priority: 4,
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-content.component.html b/static/js/directives/ui/cor-tabs/cor-tab-content.component.html
new file mode 100644
index 000000000..747ccb2c8
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-content.component.html
@@ -0,0 +1 @@
+
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts
new file mode 100644
index 000000000..a79d7db5d
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts
@@ -0,0 +1,15 @@
+import { Component } from 'ng-metadata/core';
+
+
+/**
+ * A component that is placed under a cor-tabs to wrap tab content with additional styling.
+ */
+@Component({
+ selector: 'cor-tab-content',
+ templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-content.component.html',
+ legacy: {
+ transclude: true,
+ replace: true,
+ }
+})
+export class CorTabContentComponent {}
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts b/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts
new file mode 100644
index 000000000..a9eba4945
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts
@@ -0,0 +1,83 @@
+import { CorTabComponent } from './cor-tab.component';
+import { CorTabPanelComponent } from './cor-tab-panel.component';
+
+/**
+ * Defines an interface for reading and writing the current tab state.
+ */
+export interface CorTabCurrentHandler {
+ getInitialTabId(): string
+
+ notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean)
+
+ dispose(): void
+}
+
+export function CorTabCurrentHandlerFactory(options?: any): CorTabCurrentHandler {
+ switch (options.type) {
+ case "cookie":
+ return new CookieCurrentTabHandler(options.cookieService, options.cookieName);
+ default:
+ return new LocationCurrentTabHandler(options.panel, options.$location, options.$rootScope);
+ }
+}
+
+/**
+ * Reads and writes the tab from the `tab` query parameter in the location.
+ */
+export class LocationCurrentTabHandler implements CorTabCurrentHandler {
+ private cancelWatchHandle: Function;
+
+ constructor (private panel: CorTabPanelComponent,
+ private $location: ng.ILocationService,
+ private $rootScope: ng.IRootScopeService) {
+ }
+
+ private checkLocation(): void {
+ var specifiedTabId = this.$location.search()['tab'];
+ var specifiedTab = this.panel.findTab(specifiedTabId);
+ this.panel.setActiveTab(specifiedTab);
+ }
+
+ public getInitialTabId(): string {
+ if (!this.cancelWatchHandle) {
+ this.cancelWatchHandle = this.$rootScope.$on('$routeUpdate', () => this.checkLocation());
+ }
+ return this.$location.search()['tab'];
+ }
+
+ public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) {
+ var newSearch = $.extend(this.$location.search(), {});
+ if (isDefaultTab) {
+ delete newSearch['tab'];
+ } else {
+ newSearch['tab'] = tab.tabId;
+ }
+
+ this.$location.search(newSearch);
+ }
+
+ public dispose(): void {
+ this.cancelWatchHandle();
+ }
+}
+
+/**
+ * Reads and writes the tab from a cookie,.
+ */
+export class CookieCurrentTabHandler implements CorTabCurrentHandler {
+ constructor (private CookieService: any, private cookieName: string) {}
+
+ public getInitialTabId(): string {
+ return this.CookieService.get(this.cookieName);
+ }
+
+ public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) {
+ if (isDefaultTab) {
+ this.CookieService.clear(this.cookieName);
+ } else {
+ this.CookieService.putPermanent(this.cookieName, tab.tabId);
+ }
+ }
+
+ public dispose(): void {}
+}
\ No newline at end of file
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html
new file mode 100644
index 000000000..f452537ab
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts
new file mode 100644
index 000000000..03fcc04c9
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts
@@ -0,0 +1,31 @@
+import { Component, Input, Inject, Host, OnInit } from 'ng-metadata/core';
+import { CorTabPanelComponent } from './cor-tab-panel.component';
+
+
+/**
+ * A component that creates a single tab pane under a cor-tabs component.
+ */
+@Component({
+ selector: 'cor-tab-pane',
+ templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html',
+ legacy: {
+ transclude: true,
+ }
+})
+export class CorTabPaneComponent implements OnInit {
+ @Input('@') public id: string;
+
+ // Whether this is the active tab.
+ private isActiveTab: boolean = false;
+
+ constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) {
+ }
+
+ public ngOnInit(): void {
+ this.parent.addTabPane(this);
+ }
+
+ public changeState(isActive: boolean): void {
+ this.isActiveTab = isActive;
+ }
+}
diff --git a/static/directives/cor-tab-panel.html b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.html
similarity index 100%
rename from static/directives/cor-tab-panel.html
rename to static/js/directives/ui/cor-tabs/cor-tab-panel.component.html
diff --git a/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts
new file mode 100644
index 000000000..834121f28
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts
@@ -0,0 +1,115 @@
+import { Component, Input, Inject, OnDestroy } from 'ng-metadata/core';
+import { CorTabComponent } from './cor-tab.component';
+import { CorTabPaneComponent } from './cor-tab-pane.component';
+import { CorTabCurrentHandler, LocationCurrentTabHandler, CookieCurrentTabHandler, CorTabCurrentHandlerFactory } from './cor-tab-handlers'
+
+
+/**
+ * A component that contains a cor-tabs and handles all of its logic.
+ */
+@Component({
+ selector: 'cor-tab-panel',
+ templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-panel.component.html',
+ legacy: {
+ transclude: true,
+ }
+})
+export class CorTabPanelComponent implements OnDestroy {
+ // If 'true', the currently selected tab will be remembered via a cookie and not the page URL.
+ @Input('@') public rememberCookie: string;
+
+ // The tabs under this tabs component.
+ private tabs: CorTabComponent[] = [];
+
+ // The tab panes under the tabs component, indexed by the tab id.
+ private tabPanes: {[id: string]: CorTabPaneComponent} = {};
+
+ // The currently active tab, if any.
+ private activeTab: CorTabComponent = null;
+
+ // Whether the initial tab was set.
+ private initialTabSet: boolean = false;
+
+ // The handler to use to read/write the current tab.
+ private currentTabHandler: CorTabCurrentHandler = null;
+
+ constructor(@Inject('$location') private $location: ng.ILocationService,
+ @Inject('$rootScope') private $rootScope: ng.IRootScopeService,
+ @Inject('CookieService') private CookieService: any,
+ @Inject('CorTabCurrentHandlerFactory') private CorTabCurrentHandlerFactory: (Object) => CorTabCurrentHandler) {
+ }
+
+ public ngOnDestroy(): void {
+ this.currentTabHandler.dispose();
+ }
+
+ public tabClicked(tab: CorTabComponent): void {
+ this.setActiveTab(tab);
+ }
+
+ public findTab(tabId: string): CorTabComponent {
+ if (!this.tabs.length) {
+ return null;
+ }
+
+ var tab = this.tabs.find(function(current) {
+ return current.tabId == tabId;
+ }) || this.tabs[0];
+
+ if (!this.tabPanes[tab.tabId]) {
+ return null;
+ }
+
+ return tab;
+ }
+
+ public setActiveTab(tab: CorTabComponent): void {
+ if (this.activeTab == tab) {
+ return;
+ }
+
+ if (this.activeTab != null) {
+ this.activeTab.changeState(false);
+ this.tabPanes[this.activeTab.tabId].changeState(false);
+ }
+
+ this.activeTab = tab;
+ this.activeTab.changeState(true);
+ this.tabPanes[this.activeTab.tabId].changeState(true);
+ this.currentTabHandler.notifyTabChanged(tab, this.tabs[0] == tab);
+ }
+
+ public addTab(tab: CorTabComponent): void {
+ this.tabs.push(tab);
+ this.checkInitialTab();
+ }
+
+ public addTabPane(tabPane: CorTabPaneComponent): void {
+ this.tabPanes[tabPane.id] = tabPane;
+ this.checkInitialTab();
+ }
+
+ private checkInitialTab(): void {
+ if (this.tabs.length < 1 || this.initialTabSet) {
+ return;
+ }
+
+ this.currentTabHandler = this.CorTabCurrentHandlerFactory({
+ type: this.rememberCookie ? 'cookie' : 'location',
+ cookieService: this.CookieService,
+ cookeName: this.rememberCookie,
+ panel: this,
+ $location: this.$location,
+ $rootScope: this.$rootScope,
+ });
+
+ var tabId = this.currentTabHandler.getInitialTabId();
+ var tab = this.findTab(tabId);
+ if (!tab) {
+ return;
+ }
+
+ this.initialTabSet = true;
+ this.setActiveTab(tab);
+ }
+}
diff --git a/static/js/directives/ui/cor-tabs/cor-tab.component.html b/static/js/directives/ui/cor-tabs/cor-tab.component.html
new file mode 100644
index 000000000..ca8e42f49
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab.component.html
@@ -0,0 +1,10 @@
+
+
+ {{ ::$ctrl.tabTitle }}
+
+
+
diff --git a/static/js/directives/ui/cor-tabs/cor-tab.component.ts b/static/js/directives/ui/cor-tabs/cor-tab.component.ts
new file mode 100644
index 000000000..7697d757b
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tab.component.ts
@@ -0,0 +1,46 @@
+import { Component, Input, Output, Inject, EventEmitter, Host, OnInit } from 'ng-metadata/core';
+import { CorTabPanelComponent } from './cor-tab-panel.component';
+
+
+/**
+ * A component that creates a single tab under a cor-tabs component.
+ */
+@Component({
+ selector: 'cor-tab',
+ templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab.component.html',
+ legacy: {
+ transclude: true,
+ }
+})
+export class CorTabComponent implements OnInit {
+ @Input('@') public tabId: string;
+ @Input('@') public tabTitle: string;
+
+ @Output() public tabInit: EventEmitter = new EventEmitter();
+ @Output() public tabShown: EventEmitter = new EventEmitter();
+ @Output() public tabHidden: EventEmitter = new EventEmitter();
+
+ // Whether this is the active tab.
+ private isActive: boolean = false;
+
+ constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) {
+ }
+
+ public ngOnInit(): void {
+ this.parent.addTab(this);
+ }
+
+ public changeState(isActive: boolean): void {
+ this.isActive = isActive;
+ if (isActive) {
+ this.tabInit.emit({});
+ this.tabShown.emit({});
+ } else {
+ this.tabHidden.emit({});
+ }
+ }
+
+ private tabClicked(): void {
+ this.parent.tabClicked(this);
+ }
+}
diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.component.html b/static/js/directives/ui/cor-tabs/cor-tabs.component.html
new file mode 100644
index 000000000..0e1b4bf22
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tabs.component.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.component.ts b/static/js/directives/ui/cor-tabs/cor-tabs.component.ts
new file mode 100644
index 000000000..6b58ae50e
--- /dev/null
+++ b/static/js/directives/ui/cor-tabs/cor-tabs.component.ts
@@ -0,0 +1,22 @@
+import { Component, Host, Inject } from 'ng-metadata/core';
+import { CorTabComponent } from './cor-tab.component';
+
+
+/**
+ * A component that holds the actual tabs.
+ */
+@Component({
+ selector: 'cor-tabs',
+ templateUrl: '/static/js/directives/ui/cor-tabs/cor-tabs.component.html',
+ legacy: {
+ transclude: true,
+ }
+})
+export class CorTabsComponent {
+ // If true, the tabs are in a closed state. Only applies in the mobile view.
+ private isClosed: boolean = true;
+
+ private toggleClosed(e): void {
+ this.isClosed = !this.isClosed;
+ }
+}
diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts
index ecbf57eeb..0e3509faf 100644
--- a/static/js/quay.module.ts
+++ b/static/js/quay.module.ts
@@ -13,6 +13,12 @@ import { AppPublicViewComponent } from './directives/ui/app-public-view/app-publ
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 { CorTabPanelComponent } from './directives/ui/cor-tabs/cor-tab-panel.component';
+import { CorTabContentComponent } from './directives/ui/cor-tabs/cor-tab-content.component';
+import { CorTabsComponent } from './directives/ui/cor-tabs/cor-tabs.component';
+import { CorTabComponent } from './directives/ui/cor-tabs/cor-tab.component';
+import { CorTabPaneComponent } from './directives/ui/cor-tabs/cor-tab-pane.component';
+import { CorTabCurrentHandlerFactory } from './directives/ui/cor-tabs/cor-tab-handlers';
import { ChannelIconComponent } from './directives/ui/channel-icon/channel-icon.component';
import { TagSigningDisplayComponent } from './directives/ui/tag-signing-display/tag-signing-display.component';
import { RepositorySigningConfigComponent } from './directives/ui/repository-signing-config/repository-signing-config.component';
@@ -56,6 +62,11 @@ import { QuayRequireDirective } from './directives/structural/quay-require/quay-
DurationInputComponent,
SearchBoxComponent,
TypeaheadDirective,
+ CorTabPanelComponent,
+ CorTabContentComponent,
+ CorTabsComponent,
+ CorTabComponent,
+ CorTabPaneComponent,
],
providers: [
ViewArrayImpl,
@@ -65,6 +76,7 @@ import { QuayRequireDirective } from './directives/structural/quay-require/quay-
DataFileServiceImpl,
UtilServiceImpl,
{provide: 'fileReaderFactory', useValue: () => new FileReader()},
+ {provide: 'CorTabCurrentHandlerFactory', useValue: CorTabCurrentHandlerFactory},
],
})
export class QuayModule {
diff --git a/static/partials/image-view.html b/static/partials/image-view.html
index a773d567f..beec1526d 100644
--- a/static/partials/image-view.html
+++ b/static/partials/image-view.html
@@ -15,42 +15,46 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
diff --git a/static/partials/manage-application.html b/static/partials/manage-application.html
index b5187c678..698dca5c0 100644
--- a/static/partials/manage-application.html
+++ b/static/partials/manage-application.html
@@ -20,29 +20,28 @@
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
-
+
-
+
-
+
Click the button below to generate a new
OAuth 2 Access Token .
@@ -125,10 +124,10 @@
ng-disabled="!getScopes(genScopes).length" ng-safenewtab>
Generate Access Token
-
+
-
+
Client ID:
@@ -139,10 +138,9 @@
Reset Client Secret
-
-
-
-
+
+
+
diff --git a/static/partials/org-view.html b/static/partials/org-view.html
index d750ac66c..1fd56e0bb 100644
--- a/static/partials/org-view.html
+++ b/static/partials/org-view.html
@@ -20,75 +20,75 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html
index 746345390..95da42e2a 100644
--- a/static/partials/repo-view.html
+++ b/static/partials/repo-view.html
@@ -35,91 +35,91 @@
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
diff --git a/static/partials/super-user.html b/static/partials/super-user.html
index 827a49e59..f272834bb 100644
--- a/static/partials/super-user.html
+++ b/static/partials/super-user.html
@@ -22,72 +22,71 @@
Quay Enterprise Management
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
Warning: If you are running Quay Enterprise under multiple containers, additional actions are necessary in order to apply any configuration changes made here to the entire cluster. It is recommended to make (and validate) configuration changes here, then copy your configuration to all instances and restart them.
-
+
-
+
-
-
+
-
+
-
-
+
-
+
-
+
-
-
+
+
+
diff --git a/static/partials/user-view.html b/static/partials/user-view.html
index 5bc311935..e275fe049 100644
--- a/static/partials/user-view.html
+++ b/static/partials/user-view.html
@@ -20,54 +20,54 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
-
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
Docker CLI Password
@@ -133,21 +133,20 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+