diff --git a/README.md b/README.md index 7b934a99..0d2b02da 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ between process cache and Redis. > Kotlin DSL ``` kotlin - val coskyVersion = "1.1.12"; + val coskyVersion = "1.2.0"; implementation("me.ahoo.cosky:spring-cloud-starter-cosky-config:${coskyVersion}") implementation("me.ahoo.cosky:spring-cloud-starter-cosky-discovery:${coskyVersion}") implementation("org.springframework.cloud:spring-cloud-starter-loadbalancer:3.0.3") @@ -52,7 +52,7 @@ between process cache and Redis. 4.0.0 demo - 1.1.12 + 1.2.0 @@ -101,30 +101,21 @@ logging: #### Option 1:Download the executable file -> Download [cosky-rest-api-server](https://github.com/Ahoo-Wang/cosky/releases/download/1.1.12/cosky-rest-api-1.1.12.tar) +> Download [cosky-rest-api-server](https://github.com/Ahoo-Wang/cosky/releases/download/1.2.0/cosky-rest-api-1.2.0.tar) -> tar *cosky-rest-api-1.1.12.tar* +> tar *cosky-rest-api-1.2.0.tar* ```shell -cd cosky-rest-api-1.1.12 -# Working directory: cosky-rest-api-1.1.12 +cd cosky-rest-api-1.2.0 +# Working directory: cosky-rest-api-1.2.0 bin/cosky-rest-api --server.port=8080 --cosky.redis.uri=redis://localhost:6379 ``` #### Option 2:Run On Docker ```shell -docker pull ahoowang/cosky-rest-api:1.1.12 -docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.1.12 -``` - -##### MacBook Pro (M1) - -> Please use *ahoowang/cosky-rest-api:1.1.12-armv7* - -```shell -docker pull ahoowang/cosky-rest-api:1.1.12-armv7 -docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.1.12-armv7 +docker pull ahoowang/cosky-rest-api:1.2.0 +docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.2.0 ``` #### Option 3:Run On Kubernetes @@ -152,7 +143,7 @@ spec: value: standalone - name: COSKY_REDIS_URI value: redis://redis-uri:6379 - image: ahoowang/cosky-rest-api:1.1.12 + image: ahoowang/cosky-rest-api:1.2.0 name: cosky-rest-api ports: - containerPort: 8080 @@ -196,6 +187,34 @@ spec: ![dashboard-dashboard](./docs/dashboard-dashboard.png) +### Role-based access control(RBAC) + +- cosky: Reserved username, super user, with the highest authority. When the application is launched for the first time, the super user (cosky) password will be initialized and printed on the console. Don't worry if you forget your password, you can configure `enforce-init-super-user: true`, *CoSky* will help you reinitialize the password and print it on the console. + +```log +---------------- ****** CoSky - init super user:[cosky] password:[6TrmOux4Oj] ****** ---------------- +``` + +- admin: Reserved roles, super administrator roles, have all permissions, a user can be bound to multiple roles, and a role can be bound to multiple resource operation permissions. +- Permission control granularity is namespace, read and write operations + +#### Role Permissions + +![dashboard-role](./docs/dashboard-role.png) + +##### Add Role + +![dashboard-role-add](./docs/dashboard-role-add.png) + +#### User Management + +![dashboard-user](./docs/dashboard-user.png) + +##### Add User + +![dashboard-user-add](./docs/dashboard-user-add.png) + + #### Namespace ![dashboard-namespace](./docs/dashboard-namespace.png) @@ -286,12 +305,12 @@ spec: ``` shell gradle cosky-config:jmh # or -java -jar cosky-config/build/libs/cosky-config-1.1.12-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 +java -jar cosky-config/build/libs/cosky-config-1.2.0-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 ``` ``` # JMH version: 1.29 -# VM version: JDK 11.1.121, OpenJDK 64-Bit Server VM, 11.1.121+9-LTS +# VM version: JDK 11.2.01, OpenJDK 64-Bit Server VM, 11.2.01+9-LTS # VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java # VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/cosky/cosky-config/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant # Blackhole mode: full + dont-inline hint @@ -312,12 +331,12 @@ RedisConfigServiceBenchmark.setConfig thrpt 140461.112 ``` shell gradle cosky-discovery:jmh # or -java -jar cosky-discovery/build/libs/cosky-discovery-1.1.12-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 +java -jar cosky-discovery/build/libs/cosky-discovery-1.2.0-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 ``` ``` # JMH version: 1.29 -# VM version: JDK 11.1.121, OpenJDK 64-Bit Server VM, 11.1.121+9-LTS +# VM version: JDK 11.2.01, OpenJDK 64-Bit Server VM, 11.2.01+9-LTS # VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java # VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/cosky/cosky-discovery/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant # Blackhole mode: full + dont-inline hint diff --git a/README.zh-CN.md b/README.zh-CN.md index 0ba87962..2c25faf1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -33,7 +33,7 @@ > Kotlin DSL ``` kotlin - val coskyVersion = "1.1.12"; + val coskyVersion = "1.2.0"; implementation("me.ahoo.cosky:spring-cloud-starter-cosky-config:${coskyVersion}") implementation("me.ahoo.cosky:spring-cloud-starter-cosky-discovery:${coskyVersion}") implementation("org.springframework.cloud:spring-cloud-starter-loadbalancer:3.0.3") @@ -51,7 +51,7 @@ 4.0.0 demo - 1.1.12 + 1.2.0 @@ -100,30 +100,21 @@ logging: #### 方式一:下载可执行文件 -> 下载 [rest-api-server](https://github.com/Ahoo-Wang/cosky/releases/download/1.1.12/cosky-rest-api-1.1.12.tar) +> 下载 [rest-api-server](https://github.com/Ahoo-Wang/cosky/releases/download/1.2.0/cosky-rest-api-1.2.0.tar) -> 解压 *cosky-rest-api-1.1.12.tar* +> 解压 *cosky-rest-api-1.2.0.tar* ```shell -cd cosky-rest-api-1.1.12 -# 工作目录: cosky-rest-api-1.1.12 +cd cosky-rest-api-1.2.0 +# 工作目录: cosky-rest-api-1.2.0 bin/cosky-rest-api --server.port=8080 --cosky.redis.uri=redis://localhost:6379 ``` #### 方式二:在 Docker 中运行 ```shell -docker pull ahoowang/cosky-rest-api:1.1.12 -docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.1.12 -``` - -##### MacBook Pro (M1) - -> 请使用 *ahoowang/cosky-rest-api:1.1.12-armv7* - -```shell -docker pull ahoowang/cosky-rest-api:1.1.12-armv7 -docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.1.12-armv7 +docker pull ahoowang/cosky-rest-api:1.2.0 +docker run --name cosky-rest-api -d -p 8080:8080 --link redis -e COSKY_REDIS_URI=redis://redis:6379 ahoowang/cosky-rest-api:1.2.0 ``` #### 方式三:在 Kubernetes 中运行 @@ -151,7 +142,7 @@ spec: value: standalone - name: COSKY_REDIS_URI value: redis://redis-uri:6379 - image: ahoowang/cosky-rest-api:1.1.12 + image: ahoowang/cosky-rest-api:1.2.0 name: cosky-rest-api ports: - containerPort: 8080 @@ -195,6 +186,33 @@ spec: ![dashboard-dashboard](./docs/dashboard-dashboard.png) +### 基于角色的访问控制(RBAC) + +- cosky: 保留用户名,超级用户,拥有最高权限。应用首次启动时会初始化超级用户(*cosky*)的密码,并打印在控制台。忘记密码也不用担心,可以通过配置 `enforce-init-super-user: true`,*CoSky* 会帮助你重新初始化密码并打印在控制台。 + +```log +---------------- ****** CoSky - init super user:[cosky] password:[6TrmOux4Oj] ****** ---------------- +``` + +- admin: 保留角色,超级管理员角色,拥有所有权限,一个用户可以绑定多个角色,一个角色可以绑定多个资源操作权限。 +- 权限控制粒度为命名空间,读写操作 + +#### 角色权限 + +![dashboard-role](./docs/dashboard-role.png) + +##### 添加角色 + +![dashboard-role-add](./docs/dashboard-role-add.png) + +#### 用户管理 + +![dashboard-user](./docs/dashboard-user.png) + +##### 添加用户 + +![dashboard-user-add](./docs/dashboard-user-add.png) + #### 命名空间管理 ![dashboard-namespace](./docs/dashboard-namespace.png) @@ -285,12 +303,12 @@ spec: ``` shell gradle cosky-config:jmh # or -java -jar cosky-config/build/libs/cosky-config-1.1.12-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 +java -jar cosky-config/build/libs/cosky-config-1.2.0-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 ``` ``` # JMH version: 1.29 -# VM version: JDK 11.1.121, OpenJDK 64-Bit Server VM, 11.1.121+9-LTS +# VM version: JDK 11.2.01, OpenJDK 64-Bit Server VM, 11.2.01+9-LTS # VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java # VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/cosky/config/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant # Blackhole mode: full + dont-inline hint @@ -311,12 +329,12 @@ RedisConfigServiceBenchmark.setConfig thrpt 140461.112 ``` shell gradle cosky-discovery:jmh # or -java -jar cosky-discovery/build/libs/cosky-discovery-1.1.12-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 +java -jar cosky-discovery/build/libs/cosky-discovery-1.2.0-jmh.jar -bm thrpt -t 25 -wi 1 -rf json -f 1 ``` ``` # JMH version: 1.29 -# VM version: JDK 11.1.121, OpenJDK 64-Bit Server VM, 11.1.121+9-LTS +# VM version: JDK 11.2.01, OpenJDK 64-Bit Server VM, 11.2.01+9-LTS # VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java # VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/cosky/discovery/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant # Blackhole mode: full + dont-inline hint diff --git a/cosky-dashboard/package.json b/cosky-dashboard/package.json index fa8b46b2..0be20e0d 100644 --- a/cosky-dashboard/package.json +++ b/cosky-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "cosky-dashboard", - "version": "1.1.12", + "version": "1.2.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/cosky-dashboard/src/app/api/role/RoleClient.ts b/cosky-dashboard/src/app/api/role/RoleClient.ts index 23b78f0e..58d668a9 100644 --- a/cosky-dashboard/src/app/api/role/RoleClient.ts +++ b/cosky-dashboard/src/app/api/role/RoleClient.ts @@ -15,6 +15,7 @@ import {environment} from "../../../environments/environment"; import {Observable} from "rxjs"; import {HttpClient} from "@angular/common/http"; import {ResourceActionDto} from "./ResourceActionDto"; +import {RoleDto} from "./RoleDto"; @Injectable({providedIn: 'root'}) export class RoleClient { @@ -24,13 +25,18 @@ export class RoleClient { } - getAllRole(): Observable { - return this.httpClient.get(this.apiPrefix); + getAllRole(): Observable { + return this.httpClient.get(this.apiPrefix); } - saveRole(roleName: string, resourceActionBind: ResourceActionDto[]): Observable { - const apiUrl = `${this.apiPrefix}/${roleName}`; - return this.httpClient.patch(apiUrl, {roleName, resourceActionBind}); + getResourceBind(roleName: string):Observable{ + const apiUrl = `${this.apiPrefix}/${roleName}/bind`; + return this.httpClient.get(apiUrl); + } + + saveRole(roleName: string, desc: string, resourceActionBind: ResourceActionDto[]): Observable { + const apiUrl = `${this.apiPrefix}`; + return this.httpClient.put(apiUrl, {name: roleName, desc: desc, resourceActionBind}); } removeRole(roleName: string): Observable { diff --git a/cosky-dashboard/src/app/api/role/RoleDto.ts b/cosky-dashboard/src/app/api/role/RoleDto.ts new file mode 100644 index 00000000..7a3a42c2 --- /dev/null +++ b/cosky-dashboard/src/app/api/role/RoleDto.ts @@ -0,0 +1,17 @@ +/* + * Copyright [2021-2021] [ahoo wang (https://github.com/Ahoo-Wang)]. + * 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. + */ + +export interface RoleDto { + name: string; + desc: string; +} diff --git a/cosky-dashboard/src/app/api/user/UserClient.ts b/cosky-dashboard/src/app/api/user/UserClient.ts index 1888d542..cde6938c 100644 --- a/cosky-dashboard/src/app/api/user/UserClient.ts +++ b/cosky-dashboard/src/app/api/user/UserClient.ts @@ -28,9 +28,9 @@ export class UserClient { return this.httpClient.get(this.apiPrefix); } - changePwd(username: string, oldPassword: string, newPassword: string): Observable { + changePwd(username: string, oldPassword: string, newPassword: string): Observable { const apiUrl = `${this.apiPrefix}/${username}/password`; - return this.httpClient.patch(apiUrl, {username, oldPassword, newPassword}); + return this.httpClient.patch(apiUrl, {username, oldPassword, newPassword}); } addUser(username: string, password: string): Observable { @@ -47,4 +47,8 @@ export class UserClient { return this.httpClient.patch(apiUrl, roleBind); } + unlock(username: string): Observable { + const apiUrl = `${this.apiPrefix}/${username}/lock`; + return this.httpClient.delete(apiUrl); + } } diff --git a/cosky-dashboard/src/app/app-routing.module.ts b/cosky-dashboard/src/app/app-routing.module.ts index ced087ed..efc2e3ac 100644 --- a/cosky-dashboard/src/app/app-routing.module.ts +++ b/cosky-dashboard/src/app/app-routing.module.ts @@ -21,17 +21,22 @@ import {UserComponent} from "./components/user/user.component"; import {AuthGuard} from "./security/AuthGuard"; import {RoleComponent} from "./components/role/role.component"; import {LoginComponent} from "./components/login/login.component"; +import {AuthenticatedComponent} from "./components/authenticated/authenticated.component"; const routes: Routes = [ - - {path: '', pathMatch: 'full', redirectTo: '/dashboard'}, {path: 'login', component: LoginComponent}, - {path: 'dashboard', canActivate: [AuthGuard],component: DashboardComponent}, - {path: 'namespace', canActivate: [AuthGuard],component: NamespaceComponent}, - {path: 'config', canActivate: [AuthGuard], component: ConfigComponent}, - {path: 'service', canActivate: [AuthGuard], component: ServiceComponent}, - {path: 'user', canActivate: [AuthGuard], component: UserComponent}, - {path: 'role', canActivate: [AuthGuard], component: RoleComponent} + { + path: '', canActivate: [AuthGuard], component: AuthenticatedComponent, + children: [ + {path: '', pathMatch: 'full', redirectTo: '/home'}, + {path: 'home', canActivate: [AuthGuard], component: DashboardComponent}, + {path: 'namespace', canActivate: [AuthGuard], component: NamespaceComponent}, + {path: 'config', canActivate: [AuthGuard], component: ConfigComponent}, + {path: 'service', canActivate: [AuthGuard], component: ServiceComponent}, + {path: 'user', canActivate: [AuthGuard], component: UserComponent}, + {path: 'role', canActivate: [AuthGuard], component: RoleComponent} + ] + } ]; @NgModule({ diff --git a/cosky-dashboard/src/app/app.component.html b/cosky-dashboard/src/app/app.component.html index fd8ad79d..0680b43f 100644 --- a/cosky-dashboard/src/app/app.component.html +++ b/cosky-dashboard/src/app/app.component.html @@ -1,78 +1 @@ - - - - - - - - - -
- - - - -
-
- -
- -
-
- - CoSky ©2021 - -
-
+ diff --git a/cosky-dashboard/src/app/app.component.scss b/cosky-dashboard/src/app/app.component.scss index 9fb03bcd..e69de29b 100644 --- a/cosky-dashboard/src/app/app.component.scss +++ b/cosky-dashboard/src/app/app.component.scss @@ -1,96 +0,0 @@ -/*! - * Copyright [2021-2021] [ahoo wang (https://github.com/Ahoo-Wang)]. - * 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. - */ - -:host { - display: flex; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.app-layout { - height: 100%; -} - -.menu-sidebar { - position: relative; - z-index: 10; - min-height: 100vh; - box-shadow: 2px 0 6px rgba(0,21,41,.35); -} - -.header-trigger { - height: 64px; - padding: 20px 24px; - font-size: 20px; - cursor: pointer; - transition: all .3s,padding 0s; -} - -.trigger:hover { - color: #1890ff; -} - -.sidebar-logo { - position: relative; - height: 64px; - overflow: hidden; - line-height: 64px; - background: #001529; - transition: all .3s; -} - -.sidebar-logo img { - display: inline-block; - height: 32px; - width: 32px; - vertical-align: middle; -} - -.sidebar-logo h1 { - display: inline-block; - margin: 0 0 0 20px; - color: #fff; - font-weight: 600; - font-size: 14px; - font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif; - vertical-align: middle; -} - -nz-header { - padding: 0; - width: 100%; - z-index: 2; -} - -.app-header { - position: relative; - height: 64px; - padding: 0; - background: #fff; - box-shadow: 0 1px 4px rgba(0,21,41,.08); -} - -nz-content { - margin: 24px; -} - -.inner-content { - //padding: 24px; - //background: #fff; - height: 100%; -} - -nz-footer { - text-align: center; -} diff --git a/cosky-dashboard/src/app/app.component.spec.ts b/cosky-dashboard/src/app/app.component.spec.ts index db7e70a0..cf34b6b1 100644 --- a/cosky-dashboard/src/app/app.component.spec.ts +++ b/cosky-dashboard/src/app/app.component.spec.ts @@ -11,38 +11,29 @@ * limitations under the License. */ -import {TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {RouterTestingModule} from '@angular/router/testing'; import {AppComponent} from './app.component'; +import {AuthenticatedComponent} from "./components/authenticated/authenticated.component"; describe('AppComponent', () => { + let component: AuthenticatedComponent; + let fixture: ComponentFixture; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); + declarations: [ AuthenticatedComponent ] + }) + .compileComponents(); }); - it(`should have as title 'cosky-dashboard'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('CoSky Dashboard'); + beforeEach(() => { + fixture = TestBed.createComponent(AuthenticatedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); }); - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('cosky-dashboard app is running!'); + it('should create', () => { + expect(component).toBeTruthy(); }); }); diff --git a/cosky-dashboard/src/app/app.component.ts b/cosky-dashboard/src/app/app.component.ts index 7c849a57..ed351085 100644 --- a/cosky-dashboard/src/app/app.component.ts +++ b/cosky-dashboard/src/app/app.component.ts @@ -19,8 +19,6 @@ import {Component} from '@angular/core'; styleUrls: ['./app.component.scss'] }) export class AppComponent { - title = 'CoSky Dashboard'; - isCollapsed = false; constructor() { diff --git a/cosky-dashboard/src/app/app.module.ts b/cosky-dashboard/src/app/app.module.ts index c783d814..f85cc8e3 100644 --- a/cosky-dashboard/src/app/app.module.ts +++ b/cosky-dashboard/src/app/app.module.ts @@ -60,6 +60,9 @@ import { UserComponent } from './components/user/user.component'; import { RoleComponent } from './components/role/role.component'; import { RoleEditorComponent } from './components/role/role-editor/role-editor.component'; import { UserEditorComponent } from './components/user/user-editor/user-editor.component'; +import { AuthenticatedComponent } from './components/authenticated/authenticated.component'; +import { UserChangePwdComponent } from './components/user/user-change-pwd/user-change-pwd.component'; +import { UserAddComponent } from './components/user/user-add/user-add.component'; registerLocaleData(zh); @@ -85,7 +88,10 @@ export const httpInterceptorProviders = [ UserComponent, RoleComponent, RoleEditorComponent, - UserEditorComponent + UserEditorComponent, + AuthenticatedComponent, + UserChangePwdComponent, + UserAddComponent ], imports: [ BrowserModule, diff --git a/cosky-dashboard/src/app/components/authenticated/authenticated.component.html b/cosky-dashboard/src/app/components/authenticated/authenticated.component.html new file mode 100644 index 00000000..25c6e74a --- /dev/null +++ b/cosky-dashboard/src/app/components/authenticated/authenticated.component.html @@ -0,0 +1,93 @@ + + + + + + + + + +
+ + + + +
+ + + {{currentUser.sub}} + + + +
    +
  • Change Password
  • +
  • +
  • + Sign out
  • +
+
+
+
+
+ +
+ +
+
+ + CoSky ©2021 + +
+
diff --git a/cosky-dashboard/src/app/components/authenticated/authenticated.component.scss b/cosky-dashboard/src/app/components/authenticated/authenticated.component.scss new file mode 100644 index 00000000..9fb03bcd --- /dev/null +++ b/cosky-dashboard/src/app/components/authenticated/authenticated.component.scss @@ -0,0 +1,96 @@ +/*! + * Copyright [2021-2021] [ahoo wang (https://github.com/Ahoo-Wang)]. + * 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. + */ + +:host { + display: flex; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.app-layout { + height: 100%; +} + +.menu-sidebar { + position: relative; + z-index: 10; + min-height: 100vh; + box-shadow: 2px 0 6px rgba(0,21,41,.35); +} + +.header-trigger { + height: 64px; + padding: 20px 24px; + font-size: 20px; + cursor: pointer; + transition: all .3s,padding 0s; +} + +.trigger:hover { + color: #1890ff; +} + +.sidebar-logo { + position: relative; + height: 64px; + overflow: hidden; + line-height: 64px; + background: #001529; + transition: all .3s; +} + +.sidebar-logo img { + display: inline-block; + height: 32px; + width: 32px; + vertical-align: middle; +} + +.sidebar-logo h1 { + display: inline-block; + margin: 0 0 0 20px; + color: #fff; + font-weight: 600; + font-size: 14px; + font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif; + vertical-align: middle; +} + +nz-header { + padding: 0; + width: 100%; + z-index: 2; +} + +.app-header { + position: relative; + height: 64px; + padding: 0; + background: #fff; + box-shadow: 0 1px 4px rgba(0,21,41,.08); +} + +nz-content { + margin: 24px; +} + +.inner-content { + //padding: 24px; + //background: #fff; + height: 100%; +} + +nz-footer { + text-align: center; +} diff --git a/cosky-dashboard/src/app/components/authenticated/authenticated.component.spec.ts b/cosky-dashboard/src/app/components/authenticated/authenticated.component.spec.ts new file mode 100644 index 00000000..4e6d2330 --- /dev/null +++ b/cosky-dashboard/src/app/components/authenticated/authenticated.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AuthenticatedComponent } from './authenticated.component'; + +describe('AuthenticatedComponent', () => { + let component: AuthenticatedComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AuthenticatedComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AuthenticatedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/cosky-dashboard/src/app/components/authenticated/authenticated.component.ts b/cosky-dashboard/src/app/components/authenticated/authenticated.component.ts new file mode 100644 index 00000000..873938d7 --- /dev/null +++ b/cosky-dashboard/src/app/components/authenticated/authenticated.component.ts @@ -0,0 +1,49 @@ +import {Component, OnInit} from '@angular/core'; +import {SecurityService} from "../../security/SecurityService"; +import {TokenPayload} from "../../api/authenticate/TokenPayload"; +import {NzDrawerService} from "ng-zorro-antd/drawer"; +import {UserChangePwdComponent} from "../user/user-change-pwd/user-change-pwd.component"; +import {NzMessageService} from "ng-zorro-antd/message"; + +@Component({ + selector: 'app-authenticated', + templateUrl: './authenticated.component.html', + styleUrls: ['./authenticated.component.scss'] +}) +export class AuthenticatedComponent implements OnInit { + title = 'CoSky Dashboard'; + isCollapsed = false; + currentUser: TokenPayload; + + constructor(private securityService: SecurityService + , private messageService: NzMessageService + , private drawerService: NzDrawerService) { + this.currentUser = this.securityService.getCurrentUser(); + } + + ngOnInit(): void { + + } + + + signOut() { + this.securityService.signOut(); + } + + + openChangePwd() { + const drawerRef = this.drawerService.create({ + nzTitle: `Change User:[${this.currentUser.sub}] Password`, + nzWidth: '30%', + nzContent: UserChangePwdComponent + }); + drawerRef.afterOpen.subscribe(() => { + drawerRef.getContentComponent()?.afterChange.subscribe(result => { + if (result) { + drawerRef.close('Operation successful'); + } + this.messageService.success("Password reset complete!") + }); + }); + } +} diff --git a/cosky-dashboard/src/app/components/login/login.component.html b/cosky-dashboard/src/app/components/login/login.component.html index 8127c50d..7a03236f 100644 --- a/cosky-dashboard/src/app/components/login/login.component.html +++ b/cosky-dashboard/src/app/components/login/login.component.html @@ -1,20 +1,47 @@ - - + + + + + + + + + + CoSky + ©2021 + + diff --git a/cosky-dashboard/src/app/components/login/login.component.scss b/cosky-dashboard/src/app/components/login/login.component.scss index 413653dc..07217813 100644 --- a/cosky-dashboard/src/app/components/login/login.component.scss +++ b/cosky-dashboard/src/app/components/login/login.component.scss @@ -1,4 +1,5 @@ .login-card{ - max-width: 500px; - margin: 200px auto; + width: 500px; + margin: 100px auto; + //float: right; } diff --git a/cosky-dashboard/src/app/components/login/login.component.ts b/cosky-dashboard/src/app/components/login/login.component.ts index abefbff6..e7aa24ed 100644 --- a/cosky-dashboard/src/app/components/login/login.component.ts +++ b/cosky-dashboard/src/app/components/login/login.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; -import {SecurityService} from "../../security/SecurityService"; +import {HOME_PATH, SecurityService} from "../../security/SecurityService"; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {Router} from "@angular/router"; @Component({ selector: 'app-login', @@ -12,11 +13,15 @@ export class LoginComponent implements OnInit { username!: string; password!: string; - constructor(private securityService: SecurityService, - private formBuilder: FormBuilder) { + constructor(private securityService: SecurityService + , private router: Router + , private formBuilder: FormBuilder) { } ngOnInit(): void { + if (this.securityService.authenticated()) { + this.router.navigate([HOME_PATH]) + } const controlsConfig = { username: [this.username, [Validators.required]], password: [this.password, [Validators.required]] diff --git a/cosky-dashboard/src/app/components/namespace/namespace-selector/namespace-selector.component.ts b/cosky-dashboard/src/app/components/namespace/namespace-selector/namespace-selector.component.ts index 72aa2db6..a9bc7bcb 100644 --- a/cosky-dashboard/src/app/components/namespace/namespace-selector/namespace-selector.component.ts +++ b/cosky-dashboard/src/app/components/namespace/namespace-selector/namespace-selector.component.ts @@ -32,7 +32,13 @@ export class NamespaceSelectorComponent implements OnInit { this.currentNamespace = this.namespaceContext.getCurrent(); this.namespaceClient.getNamespaces().subscribe(namespaces => { this.namespaces = namespaces; - if (!this.currentNamespace && namespaces.length > 0) { + + if (namespaces.length === 0) { + return; + } + + if (!this.currentNamespace + || namespaces.indexOf(this.currentNamespace) < 0) { this.currentNamespace = namespaces[0]; this.onNamespaceSelected(); } diff --git a/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.html b/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.html index 2403a23b..e424692e 100644 --- a/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.html +++ b/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.html @@ -1 +1,71 @@ -

role-editor works!

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + Namespace + Action + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.ts b/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.ts index f9fc5fe6..59d23d0b 100644 --- a/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.ts +++ b/cosky-dashboard/src/app/components/role/role-editor/role-editor.component.ts @@ -1,4 +1,9 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {ResourceActionDto} from "../../../api/role/ResourceActionDto"; +import {NamespaceClient} from "../../../api/namespace/NamespaceClient"; +import {RoleClient} from "../../../api/role/RoleClient"; +import {RoleDto} from "../../../api/role/RoleDto"; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-role-editor', @@ -6,11 +11,59 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; styleUrls: ['./role-editor.component.scss'] }) export class RoleEditorComponent implements OnInit { - @Input() role!: string|null; + @Input() role!: RoleDto | null; @Output() afterSave: EventEmitter = new EventEmitter(); - constructor() { } + + resourceActionBind: ResourceActionDto[] = []; + namespaces: string[] = []; + roleName!: string; + desc!: string; + editorForm!: FormGroup; + isAdd!: boolean; + + constructor(private namespaceClient: NamespaceClient, private roleClient: RoleClient, + private formBuilder: FormBuilder) { + + } + + loadRole() { + if (!this.role) { + this.isAdd = true; + return; + } + this.isAdd = false; + this.roleName = this.role.name; + this.desc = this.role.desc; + this.roleClient.getResourceBind(this.roleName).subscribe(resp => { + this.resourceActionBind = resp; + }) + } ngOnInit(): void { + this.loadRole(); + const controlsConfig = { + roleName: [this.roleName, [Validators.required]], + desc: [this.desc, [Validators.required]] + }; + if (!this.isAdd) { + controlsConfig.roleName = [this.roleName]; + } + this.editorForm = this.formBuilder.group(controlsConfig); + this.namespaceClient.getNamespaces().subscribe(resp => this.namespaces = resp); } + + addResourceAction() { + this.resourceActionBind = [...this.resourceActionBind, {namespace: '', action: 'r'}] + } + + removeResourceAction(resourceAction: ResourceActionDto) { + this.resourceActionBind = this.resourceActionBind.filter(resource => resource != resourceAction); + } + + saveRole() { + this.roleClient.saveRole(this.roleName, this.desc, this.resourceActionBind).subscribe(resp => { + this.afterSave.emit(true) + }) + } } diff --git a/cosky-dashboard/src/app/components/role/role.component.html b/cosky-dashboard/src/app/components/role/role.component.html index 8015044b..3a1cfaef 100644 --- a/cosky-dashboard/src/app/components/role/role.component.html +++ b/cosky-dashboard/src/app/components/role/role.component.html @@ -1,5 +1,5 @@
-
+
@@ -11,13 +11,14 @@ Role Name + Desc Action - {{role}} - + {{role.name}} + {{role.desc}} + diff --git a/cosky-dashboard/src/app/components/user/user-add/user-add.component.scss b/cosky-dashboard/src/app/components/user/user-add/user-add.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/cosky-dashboard/src/app/components/user/user-add/user-add.component.spec.ts b/cosky-dashboard/src/app/components/user/user-add/user-add.component.spec.ts new file mode 100644 index 00000000..74a02a1f --- /dev/null +++ b/cosky-dashboard/src/app/components/user/user-add/user-add.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserAddComponent } from './user-add.component'; + +describe('UserAddComponent', () => { + let component: UserAddComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserAddComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/cosky-dashboard/src/app/components/user/user-add/user-add.component.ts b/cosky-dashboard/src/app/components/user/user-add/user-add.component.ts new file mode 100644 index 00000000..b5fb2471 --- /dev/null +++ b/cosky-dashboard/src/app/components/user/user-add/user-add.component.ts @@ -0,0 +1,44 @@ +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {UserClient} from "../../../api/user/UserClient"; +import {RoleClient} from "../../../api/role/RoleClient"; +import {RoleDto} from "../../../api/role/RoleDto"; + +@Component({ + selector: 'app-user-add', + templateUrl: './user-add.component.html', + styleUrls: ['./user-add.component.scss'] +}) +export class UserAddComponent implements OnInit { + @Output() afterAdd: EventEmitter = new EventEmitter(); + addForm!: FormGroup; + username!: string; + password!: string; + roleBind!: string[]; + roles!: RoleDto[]; + + constructor(private userClient: UserClient, + private roleClient: RoleClient, + private formBuilder: FormBuilder) { + } + + ngOnInit(): void { + this.roleClient.getAllRole().subscribe(resp => { + this.roles = resp; + }) + const controlsConfig = { + username: [this.username, [Validators.required]], + password: [this.password, [Validators.required]], + roleBind: [this.roleBind, [Validators.required]] + }; + this.addForm = this.formBuilder.group(controlsConfig); + } + + addUser() { + this.userClient.addUser(this.username, this.password).subscribe(resp => { + this.userClient.bindRole(this.username, this.roleBind).subscribe(bindResp => { + this.afterAdd.emit(true); + }) + }); + } +} diff --git a/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.html b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.html new file mode 100644 index 00000000..38eb79ba --- /dev/null +++ b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.html @@ -0,0 +1,17 @@ +
+ + + + + + + + + + + + + + + +
diff --git a/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.scss b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.spec.ts b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.spec.ts new file mode 100644 index 00000000..bbb074f9 --- /dev/null +++ b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserChangePwdComponent } from './user-change-pwd.component'; + +describe('UserChangePwdComponent', () => { + let component: UserChangePwdComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserChangePwdComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserChangePwdComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.ts b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.ts new file mode 100644 index 00000000..e7d7bde3 --- /dev/null +++ b/cosky-dashboard/src/app/components/user/user-change-pwd/user-change-pwd.component.ts @@ -0,0 +1,36 @@ +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {UserClient} from "../../../api/user/UserClient"; +import {SecurityService} from "../../../security/SecurityService"; + +@Component({ + selector: 'app-user-change-pwd', + templateUrl: './user-change-pwd.component.html', + styleUrls: ['./user-change-pwd.component.scss'] +}) +export class UserChangePwdComponent implements OnInit { + editorForm!: FormGroup; + oldPassword!: string; + newPassword!: string; + @Output() afterChange: EventEmitter = new EventEmitter(); + + constructor(private userClient: UserClient, + private securityService: SecurityService, + private formBuilder: FormBuilder) { + } + + ngOnInit(): void { + const controlsConfig = { + oldPassword: [this.oldPassword, [Validators.required]], + newPassword: [this.newPassword, [Validators.required]] + }; + this.editorForm = this.formBuilder.group(controlsConfig); + } + + changePwd() { + const username = this.securityService.getCurrentUser().sub; + this.userClient.changePwd(username, this.oldPassword, this.newPassword).subscribe(result => { + this.afterChange.emit(true); + }); + } +} diff --git a/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.html b/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.html index 131698a8..524415ce 100644 --- a/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.html +++ b/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.html @@ -1,28 +1,16 @@ -
- - - - - + + + + + + - - - - - - - - - - - - - + diff --git a/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.ts b/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.ts index 55c964b8..90d206da 100644 --- a/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.ts +++ b/cosky-dashboard/src/app/components/user/user-editor/user-editor.component.ts @@ -3,6 +3,7 @@ import {UserDto} from "../../../api/user/UserDto"; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {UserClient} from "../../../api/user/UserClient"; import {RoleClient} from "../../../api/role/RoleClient"; +import {RoleDto} from "../../../api/role/RoleDto"; @Component({ selector: 'app-user-editor', @@ -10,13 +11,10 @@ import {RoleClient} from "../../../api/role/RoleClient"; styleUrls: ['./user-editor.component.scss'] }) export class UserEditorComponent implements OnInit { - @Input() user!: UserDto | null; + @Input() user!: UserDto; @Output() afterSave: EventEmitter = new EventEmitter(); editorForm!: FormGroup; - username!: string; - password!: string; - roleBind!: string[]; - roles!: string[]; + roles!: RoleDto[]; constructor(private userClient: UserClient, private roleClient: RoleClient, @@ -24,26 +22,19 @@ export class UserEditorComponent implements OnInit { } ngOnInit(): void { - if (this.user) { - this.username = this.user.username; - this.roleBind = this.user.roleBind; - } this.roleClient.getAllRole().subscribe(resp => { this.roles = resp; }) const controlsConfig = { - username: [this.username, [Validators.required]], - password: [this.password, [Validators.required]] + roleBind: [this.user.roleBind, [Validators.required]] }; this.editorForm = this.formBuilder.group(controlsConfig); } - addUser() { - this.userClient.addUser(this.username, this.password).subscribe(resp => { - this.userClient.bindRole(this.username, this.roleBind).subscribe(bindResp => { - this.afterSave.emit(true); - }) - }); + bindRole() { + this.userClient.bindRole(this.user.username, this.user.roleBind).subscribe(bindResp => { + this.afterSave.emit(true); + }) } } diff --git a/cosky-dashboard/src/app/components/user/user.component.html b/cosky-dashboard/src/app/components/user/user.component.html index 118e32a2..854c173d 100644 --- a/cosky-dashboard/src/app/components/user/user.component.html +++ b/cosky-dashboard/src/app/components/user/user.component.html @@ -1,6 +1,7 @@
-
-
@@ -33,6 +34,13 @@ > +