提交 bff54982 编写于 作者: M Marcin Maciaszczyk

Merge branch 'angular-migration' of github.com:kubernetes/dashboard into angular-migration

......@@ -26,12 +26,34 @@ export class CardComponent {
@Input() withFooter = false;
@Input() withTitle = true;
@Input() expandable = true;
@Input() titleClasses = '';
@Input()
set titleClasses(val: string) {
this.classes_ = val.split(/\s+/);
}
@Input() expanded = true;
private classes_: string[] = [];
expand(): void {
if (this.expandable) {
this.expanded = !this.expanded;
}
}
getTitleClasses(): {[clsName: string]: boolean} {
const ngCls = {} as {[clsName: string]: boolean};
if (!this.expanded) {
ngCls['kd-minimized-card-header'] = true;
}
if (this.expandable) {
ngCls['kd-card-header'] = true;
}
for (const cls of this.classes_) {
ngCls[cls] = true;
}
return ngCls;
}
}
......@@ -17,10 +17,9 @@ limitations under the License.
<mat-card [ngClass]="{'kd-minimized-card': !expanded}">
<mat-card-title *ngIf="withTitle"
(click)="expand()"
[ngClass]="{'kd-minimized-card-header': !expanded, 'kd-card-header': expandable}"
[ngClass]="getTitleClasses()"
fxLayoutAlign=" center">
<div class="kd-card-title"
[ngClass]="[titleClasses]"
fxFlex>
<ng-content select="[title]"></ng-content>
</div>
......
......@@ -69,6 +69,7 @@ import {ServiceListComponent} from './resourcelist/service/component';
import {StatefulSetListComponent} from './resourcelist/statefulset/component';
import {StorageClassListComponent} from './resourcelist/storageclass/component';
import {TextInputComponent} from './textinput/component';
import {UploadFileComponent} from './uploadfile/component';
import {ZeroStateComponent} from './zerostate/component';
@NgModule({
......@@ -128,6 +129,7 @@ import {ZeroStateComponent} from './zerostate/component';
ActionbarDetailActionsComponent,
ActionbarDetailDeleteComponent,
ActionbarDetailEditComponent,
UploadFileComponent,
],
exports: [
AllocationChartComponent,
......@@ -174,6 +176,7 @@ import {ZeroStateComponent} from './zerostate/component';
ActionbarDetailActionsComponent,
ActionbarDetailDeleteComponent,
ActionbarDetailEditComponent,
UploadFileComponent,
],
entryComponents: [
ChipDialog,
......
......@@ -21,6 +21,7 @@ import {overviewState} from '../../../overview/state';
import {NAMESPACE_STATE_PARAM} from '../../params/params';
import {NamespaceService} from '../../services/global/namespace';
import {NotificationSeverity, NotificationsService} from '../../services/global/notifications';
import {KdStateService} from '../../services/global/state';
import {EndpointManager, Resource} from '../../services/resource/endpoint';
import {ResourceService} from '../../services/resource/resource';
......@@ -46,7 +47,8 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy, AfterViewI
constructor(
private readonly state_: StateService, private readonly namespaceService_: NamespaceService,
private readonly namespace_: ResourceService<NamespaceList>,
private readonly dialog_: MatDialog, private readonly kdState_: KdStateService) {}
private readonly dialog_: MatDialog, private readonly kdState_: KdStateService,
private readonly notifications_: NotificationsService) {}
ngOnInit(): void {
this.allNamespacesKey = this.namespaceService_.getAllNamespacesKey();
......@@ -125,7 +127,14 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy, AfterViewI
.subscribe(
namespaceList => {
this.namespaces = namespaceList.namespaces.map(n => n.objectMeta.name);
this.namespacesInitialized_ = true;
if (namespaceList.errors.length === 0) {
this.namespacesInitialized_ = true;
} else {
for (const err of namespaceList.errors) {
this.notifications_.push(err.ErrStatus.message, NotificationSeverity.error);
}
}
},
undefined,
() => {
......
// Copyright 2017 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {HTMLInputEvent, KdFile} from '@api/frontendapi';
@Component({
selector: 'kd-upload-file',
templateUrl: 'template.html',
styleUrls: ['style.scss'],
})
export class UploadFileComponent {
@Input() label: string;
@Output() onLoad = new EventEmitter<KdFile>();
filename: string;
onChange(event: HTMLInputEvent): void {
if (event.target.files.length > 0) {
this.readFile(event.target.files[0]);
}
}
// TODO handle error somehow
readFile(file: File): void {
this.filename = file.name;
const reader = new FileReader();
reader.onload = (event: ProgressEvent) => {
const content = (event.target as FileReader).result;
this.onLoad.emit({
name: this.filename,
content,
} as KdFile);
};
reader.readAsText(file);
}
}
// Copyright 2017 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@import '../../../variables';
.kd-upload-button {
margin-left: 2 * $baseline-grid;
margin-top: .5 * $baseline-grid;
}
<!--
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--<div fxLayout="row"
fxLayoutAlign="space-between start">
<input matInput
id="fileinput"
type="file">
<div fxFlex>
<mat-input-container class="kd-upload-file-container">
<label>{{label}}</label>
<input matInput
id="textinput"
[(ngModel)]="fileName"
[readonly]="true"
required>
</mat-input-container>
</div>
<button mat-button
id="uploadButton"
color="primary"
class="kd-upload-button"
aria-label="attach_file">
<label class="kd-upload-label">
<mat-icon>more_horiz</mat-icon>
</label>
</button>
</div>
-->
<div fxLayout="row"
fxLayoutAlign="space-between start">
<mat-input-container fxFlex>
<input matInput
[placeholder]="label"
[ngModel]="filename"
[readonly]="true"
(click)="fileInput.click()" />
</mat-input-container>
<button mat-button
color="primary"
(click)="fileInput.click()"
class="kd-upload-button">
<mat-icon>more_horiz</mat-icon>
</button>
<input hidden
type="file"
#fileInput
(change)="onChange($event)" />
</div>
......@@ -14,11 +14,11 @@
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {onLogin} from '@api/frontendapi';
import {StateService, TransitionService} from '@uirouter/angular';
import {TargetState, Transition} from '@uirouter/core';
import {CookieService} from 'ngx-cookie-service';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
import {AuthResponse, CsrfToken, K8sError, LoginSpec, LoginStatus} from 'typings/backendapi';
import {errorState} from '../../../error/state';
......@@ -53,7 +53,7 @@ export class AuthService {
}
/** Sends a login request to the backend with filled in login spec structure. */
login(loginSpec: LoginSpec): Subscription {
login(loginSpec: LoginSpec, onLoginCb: onLogin): void {
const loginObs =
this.csrfTokenService_.getTokenForAction('login').switchMap<CsrfToken, AuthResponse>(
csrfToken => {
......@@ -62,12 +62,13 @@ export class AuthService {
{headers: new HttpHeaders().set(this.config_.csrfHeaderName, csrfToken.token)});
});
return loginObs.subscribe(
loginObs.first().subscribe(
authResponse => {
if (authResponse.jweToken.length !== 0 && authResponse.errors.length === 0) {
this.setTokenCookie_(authResponse.jweToken);
}
return authResponse.errors;
onLoginCb(authResponse.errors);
},
err => {
return Observable.throw(err);
......
// Copyright 2017 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {Observable} from 'rxjs/Observable';
import {CONFIG} from '../../../index.config';
/* tslint:disable */
// We can disable tslint for this file as any is required here.
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private readonly cookies_: CookieService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Filter requests made to our backend starting with 'api/v1' and append request header
// with token stored in a cookie.
if (req.url.startsWith('api/v1')) {
const authReq = req.clone({
headers: req.headers.set(
CONFIG.authTokenHeaderName, this.cookies_.get(CONFIG.authTokenCookieName))
});
return next.handle(authReq);
}
return next.handle(req);
}
}
/* tslint:enable */
......@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {APP_INITIALIZER, Injector, NgModule} from '@angular/core';
import {DialogsModule} from '../../dialogs/module';
......@@ -24,6 +25,7 @@ import {BreadcrumbsService} from './breadcrumbs';
import {ConfigService} from './config';
import {CsrfTokenService} from './csrftoken';
import {GlobalSettingsService} from './globalsettings';
import {AuthInterceptor} from './interceptor';
import {LocalSettingsService} from './localsettings';
import {NamespaceService} from './namespace';
import {NotificationsService} from './notifications';
......@@ -45,6 +47,11 @@ import {VerberService} from './verber';
useFactory: init,
deps: [GlobalSettingsService, LocalSettingsService, ConfigService],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
}
],
})
......
......@@ -14,6 +14,7 @@
import {Injectable} from '@angular/core';
import {StateService} from '@uirouter/core';
import {CONFIG} from '../../../index.config';
import {NAMESPACE_STATE_PARAM} from '../../params/params';
@Injectable()
......@@ -21,7 +22,7 @@ export class NamespaceService {
/**
* Default namespace.
*/
private readonly defaultNamespace_ = 'default';
private readonly defaultNamespace_ = CONFIG.defaultNamespace;
/**
* Internal key for empty selection. To differentiate empty string from nulls.
*/
......
......@@ -12,32 +12,103 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Component, OnInit} from '@angular/core';
import {AuthenticationMode, EnabledAuthenticationModes, LoginSpec} from '@api/backendapi';
import {KdFile} from '@api/frontendapi';
import {StateService} from '@uirouter/core';
import {K8SError} from '../common/errors/errors';
import {AuthService} from '../common/services/global/authentication';
import {overviewState} from '../overview/state';
enum LoginModes {
Kubeconfig = 'Kubeconfig',
Basic = 'Basic',
Token = 'Token',
Kubeconfig = 'kubeconfig',
Basic = 'basic',
Token = 'token',
}
@Component({selector: 'kd-login', templateUrl: './template.html', styleUrls: ['./style.scss']})
export class LoginComponent {
export class LoginComponent implements OnInit {
loginModes = LoginModes;
selectedAuthenticationMode = LoginModes.Token;
selectedAuthenticationMode = LoginModes.Kubeconfig;
// TODO handle errors
errors: K8SError[];
constructor(private readonly authService_: AuthService, private readonly state_: StateService) {}
private enabledAuthenticationModes_: AuthenticationMode[] = [];
private kubeconfig_: string;
private token_: string;
private username_: string;
private password_: string;
getSupportedAuthenticationModes(): string[] {
return Object.keys(LoginModes);
constructor(
private readonly authService_: AuthService, private readonly state_: StateService,
private readonly httpClient: HttpClient) {}
ngOnInit(): void {
this.httpClient.get<EnabledAuthenticationModes>('api/v1/login/modes')
.subscribe((enabledModes: EnabledAuthenticationModes) => {
this.enabledAuthenticationModes_ = enabledModes.modes;
});
}
getEnabledAuthenticationModes(): AuthenticationMode[] {
if (this.enabledAuthenticationModes_.length > 0 &&
this.enabledAuthenticationModes_.indexOf(LoginModes.Kubeconfig) < 0) {
// Push this option to the beginning of the list
this.enabledAuthenticationModes_.splice(0, 0, LoginModes.Kubeconfig);
}
return this.enabledAuthenticationModes_;
}
login(): void {}
login(): void {
this.authService_.login(this.getLoginSpec_(), (errors: K8SError[]) => {
if (errors.length > 0) {
this.errors = errors;
return;
}
this.state_.go(overviewState.name);
});
}
skip(): void {
this.authService_.skipLoginPage(true);
this.state_.go(overviewState.name);
}
onChange(event: Event&KdFile): void {
switch (this.selectedAuthenticationMode) {
case (LoginModes.Kubeconfig):
this.onFileLoad_(event as KdFile);
return;
case (LoginModes.Token):
this.token_ = (event.target as HTMLInputElement).value;
return;
case (LoginModes.Basic):
if ((event.target as HTMLInputElement).id === 'username') {
this.username_ = (event.target as HTMLInputElement).value;
} else {
this.password_ = (event.target as HTMLInputElement).value;
}
default:
}
}
private onFileLoad_(file: KdFile): void {
this.kubeconfig_ = file.content;
}
private getLoginSpec_(): LoginSpec {
switch (this.selectedAuthenticationMode) {
case (LoginModes.Kubeconfig):
return {kubeConfig: this.kubeconfig_} as LoginSpec;
case (LoginModes.Token):
return {token: this.token_} as LoginSpec;
case (LoginModes.Basic):
return {username: this.username_, password: this.password_} as LoginSpec;
default:
return {} as LoginSpec;
}
}
}
......@@ -16,21 +16,21 @@
.kd-login-container {
display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(3, 1fr);
}
.kd-login-card {
grid-column: 3 / 9;
grid-column: 3 / 8;
grid-row: 2;
}
.kd-login-mode-description {
padding: $baseline-grid 0 (2 * $baseline-grid) (3.5 * $baseline-grid);
padding: $baseline-grid (3.5 * $baseline-grid) (2 * $baseline-grid);
}
.kd-login-input {
padding: 0 $baseline-grid 0 (3.5 * $baseline-grid);
padding: 0 (3.5 * $baseline-grid);
}
.kd-login-button {
......
......@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<!--<kd-warnings warnings="$ctrl.errors"
class="kd-login-error"></kd-warnings>-->
<div class="kd-login-container kd-bg-background"
fxFlex>
<kd-card titleClasses="kd-card-top-radius kd-bg-primary kd-accent"
......@@ -27,18 +25,22 @@ limitations under the License.
(ngSubmit)="login()">
<mat-radio-group name="login"
[(ngModel)]="selectedAuthenticationMode">
<div *ngFor="let loginMode of getSupportedAuthenticationModes()">
<mat-radio-button [value]="loginMode"
<div *ngFor="let mode of getEnabledAuthenticationModes()">
<mat-radio-button [value]="mode"
color="primary">
{{loginMode}}
<ng-container [ngSwitch]="mode">
<ng-container *ngSwitchCase="loginModes.Kubeconfig">Kubeconfig</ng-container>
<ng-container *ngSwitchCase="loginModes.Basic">Basic</ng-container>
<ng-container *ngSwitchCase="loginModes.Token">Token</ng-container>
</ng-container>
</mat-radio-button>
<div class="kd-login-mode-description"
[ngSwitch]="loginMode">
[ngSwitch]="mode">
<ng-container *ngSwitchCase="loginModes.Kubeconfig">
Please select the kubeconfig file that you have created to configure access to the cluster. To find out more about how to configure and use kubeconfig file, please refer to the <a href='https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/'>Configure Access to Multiple Clusters</a> section.
</ng-container>
<ng-container *ngSwitchCase="loginModes.Basic">
Basic login description.
Make sure that support for basic authentication is enabled in the cluster. To find out more about how to configure basic authentication, please refer to the <a href="https://kubernetes.io/docs/admin/authentication/">Authenticating</a> and <a href="https://kubernetes.io/docs/admin/authorization/abac/">ABAC Mode</a> sections.
</ng-container>
<ng-container *ngSwitchCase="loginModes.Token">
Every Service Account has a Secret with valid Bearer Token that can be used to log in to Dashboard. To find out more about how to configure and use Bearer Tokens, please refer to the <a href='https://kubernetes.io/docs/admin/authentication/'>Authentication</a> section.
......@@ -51,9 +53,12 @@ limitations under the License.
<mat-form-field *ngSwitchCase="loginModes.Token"
class="kd-login-input">
<input matInput
placeholder="Token"
id="token"
name="token"
placeholder="Enter token"
type="password"
required>
required
(change)="onChange($event)">
</mat-form-field>
<div *ngSwitchCase="loginModes.Basic"
......@@ -64,7 +69,8 @@ limitations under the License.
name="username"
matInput
placeholder="Username"
required>
required
(change)="onChange($event)">
</mat-form-field>
<mat-form-field fxFlex
......@@ -74,13 +80,15 @@ limitations under the License.
matInput
placeholder="Password"
type="password"
required>
required
(change)="onChange($event)">
</mat-form-field>
</div>
<div *ngSwitchCase="loginModes.Kubeconfig"
class="kd-login-input">
kubeconfig placeholder
<kd-upload-file label="Choose kubeconfig file"
(onLoad)="onChange($event)"></kd-upload-file>
</div>
</ng-container>
......@@ -94,6 +102,7 @@ limitations under the License.
</button>
<button mat-button
color="primary"
type="button"
class="kd-login-button"
(click)="skip()">
Skip
......@@ -103,33 +112,3 @@ limitations under the License.
</div>
</kd-card>
</div>
<!--<kd-content-card class="kd-login-card">
<kd-title>
[[Kubernetes Dashboard|Title shown on login page on login card]]
</kd-title>
<kd-content>
<kd-login-options on-change="$ctrl.onOptionChange()">
<kd-token-login ng-if="$ctrl.isAuthenticationModeEnabled($ctrl.supportedAuthenticationModes.TOKEN)"
title="[[Token|Token option label shown on login page]]"
desc="[[Every Service Account has a Secret with valid Bearer Token that can be used to log in to Dashboard. To find out more about how to configure and use Bearer Tokens, please refer to the <a href='https://kubernetes.io/docs/admin/authentication/'>Authentication</a> section.|]]"
on-update="$ctrl.onUpdate(loginSpec)"></kd-token-login>
<kd-basic-login ng-if="$ctrl.isAuthenticationModeEnabled($ctrl.supportedAuthenticationModes.BASIC)"
title="[[Basic|Basic option label shown on login page]]"
on-update="$ctrl.onUpdate(loginSpec)"></kd-basic-login>
<kd-kube-config-login title="[[Kubeconfig|Kubeconfig option label shown on login page]]"
desc="[[Please select the kubeconfig file that you have created to configure access to the cluster. To find out more about how to configure and use kubeconfig file, please refer to the <a href='https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/'>Configure Access to Multiple Clusters</a> section.|]]"
on-update="$ctrl.onUpdate(loginSpec)"></kd-kube-config-login>
</kd-login-options>
<md-button class="md-raised md-primary"
type="submit">
[[Sign in|Text shown on the sign in button on login page]]
</md-button>
<md-button class="md-primary"
ng-click="$ctrl.skip()">
[[Skip|Text shown on skip button on login page]]
</md-button>
</kd-content>
</kd-content-card>-->
......@@ -31,6 +31,7 @@ export class GlobalSettingsComponent implements OnInit {
// Keep it in sync with ConcurrentSettingsChangeError constant from the backend.
private readonly concurrentChangeErr_ = 'settings changed since last reload';
settings: GlobalSettings = {} as GlobalSettings;
hasLoadError = false;
constructor(
private readonly settings_: GlobalSettingsService, private readonly dialog_: MatDialog,
......@@ -58,8 +59,8 @@ export class GlobalSettingsComponent implements OnInit {
this.settings.autoRefreshTimeInterval = this.settings_.getAutoRefreshTimeInterval();
}
onLoadError(err: KdError|K8sError): void {
this.state_.go(errorState.name, new ErrorStateParams(err, ''));
onLoadError(_err: KdError|K8sError): void {
this.hasLoadError = true;
}
save(form: NgForm): void {
......
......@@ -15,7 +15,8 @@ limitations under the License.
-->
<kd-card [initialized]="isInitialized()"
[expandable]="true">
[expandable]="true"
*ngIf="!hasLoadError">
<div title
i18n>Global settings</div>
<div content>
......
......@@ -19,7 +19,7 @@ limitations under the License.
i18n>Local settings</div>
<div content>
<p i18n>
Local settings are stored in the browser cookies, so they are not synchronized between multiple devices. Changes are applied automatically on every input change.
Local settings are stored in the browser cookies, so they are not synchronized between multiple devices. Changes are applied automatically on every change.
</p>
<br>
<kd-settings-entry key="Dark theme"
......
......@@ -913,10 +913,9 @@ export interface TokenRefreshSpec { jweToken: string; }
export interface LoginModesResponse { modes: string[]; }
export interface SupportedAuthenticationModes {
// TODO TOKEN: AuthenticationMode;
// TODO BASIC: AuthenticationMode;
}
export type AuthenticationMode = string;
export interface EnabledAuthenticationModes { modes: AuthenticationMode[]; }
export interface SystemBanner {
message: string;
......
......@@ -31,6 +31,8 @@ export type ColumnWhenCallback = () => boolean;
export type onSettingsLoadCallback = (settings?: GlobalSettings) => void;
export type onSettingsFailCallback = (err?: KdError|K8sError) => void;
export type onLogin = (errors?: K8sError[]) => void;
export interface KnownErrors { unauthorized: KdError; }
export interface KdError {
......@@ -61,3 +63,10 @@ export interface ActionColumn {
setTypeMeta(typeMeta: TypeMeta): void;
setObjectMeta(objectMeta: ObjectMeta): void;
}
export interface HTMLInputEvent extends Event { target: HTMLInputElement&EventTarget; }
export interface KdFile {
name: string;
content: string;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册