Create Boiler Template project
This commit is contained in:
commit
35ebc530c4
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
**/wwwroot/lib/** linguist-vendored
|
||||
**/wwwroot/css/** linguist-vendored
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# IDEs and editors
|
||||
.vs/
|
||||
obj/
|
||||
bin/
|
||||
aspnet-core/src/MeetingSchedule.Web.Host/App_Data/Logs/
|
||||
aspnet-core/src/MeetingSchedule.Web.Mvc/App_Data/Logs/
|
||||
aspnet-core/src/MeetingSchedule.Migrator/Logs/Logs.txt
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 ASP.NET Boilerplate
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
37
README.md
Normal file
37
README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Important
|
||||
|
||||
Issues of this repository are tracked on https://github.com/aspnetboilerplate/aspnetboilerplate. Please create your issues on https://github.com/aspnetboilerplate/aspnetboilerplate/issues.
|
||||
|
||||
# Introduction
|
||||
|
||||
This is a template to create **ASP.NET Core MVC / Angular** based startup projects for [ASP.NET Boilerplate](https://aspnetboilerplate.com/Pages/Documents). It has 2 different versions:
|
||||
|
||||
1. [ASP.NET Core MVC & jQuery](https://aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template-Core) (server rendered multi-page application).
|
||||
2. [ASP.NET Core & Angular](https://aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template-Angular) (single page application).
|
||||
|
||||
User Interface is based on [AdminLTE theme](https://github.com/ColorlibHQ/AdminLTE).
|
||||
|
||||
# Download
|
||||
|
||||
Create & download your project from https://aspnetboilerplate.com/Templates
|
||||
|
||||
# Screenshots
|
||||
|
||||
#### Sample Dashboard Page
|
||||

|
||||
|
||||
#### User Creation Modal
|
||||

|
||||
|
||||
#### Login Page
|
||||
|
||||

|
||||
|
||||
# Documentation
|
||||
|
||||
* [ASP.NET Core MVC & jQuery version.](https://aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template-Core)
|
||||
* [ASP.NET Core & Angular version.](https://aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template-Angular)
|
||||
|
||||
# License
|
||||
|
||||
[MIT](LICENSE).
|
BIN
_screenshots/ui-home.png
Normal file
BIN
_screenshots/ui-home.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
BIN
_screenshots/ui-login.png
Normal file
BIN
_screenshots/ui-login.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
_screenshots/ui-user-create-modal.png
Normal file
BIN
_screenshots/ui-user-create-modal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
12
angular/.browserslistrc
Normal file
12
angular/.browserslistrc
Normal file
@ -0,0 +1,12 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
12
angular/.editorconfig
Normal file
12
angular/.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
43
angular/.gitignore
vendored
Normal file
43
angular/.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
.vs/
|
||||
[Oo]bj/
|
||||
bin/
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
#System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
16
angular/.vscode/launch.json
vendored
Normal file
16
angular/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:4200",
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
3
angular/Dockerfile
Normal file
3
angular/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM nginx
|
||||
|
||||
COPY . /usr/share/nginx/html
|
24
angular/MeetingSchedule.AngularUI.csproj
Normal file
24
angular/MeetingSchedule.AngularUI.csproj
Normal file
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AssemblyName>angular</AssemblyName>
|
||||
<PackageId>angular</PackageId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="node_modules\**" />
|
||||
<EmbeddedResource Remove="node_modules\**" />
|
||||
<None Remove="node_modules\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="dist" />
|
||||
<None Include="app.config" />
|
||||
<None Update="wwwroot\**\*;web.config">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Dockerfile">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
33
angular/MeetingSchedule.AngularUI.sln
Normal file
33
angular/MeetingSchedule.AngularUI.sln
Normal file
@ -0,0 +1,33 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.4
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeetingSchedule.AngularUI", "MeetingSchedule.AngularUI.csproj", "{11BD8782-23F0-45A0-9A00-A213373B0F5D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{11BD8782-23F0-45A0-9A00-A213373B0F5D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
27
angular/Properties/launchSettings.json
Normal file
27
angular/Properties/launchSettings.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14424/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"MeetingSchedule.AngularUI": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
angular/README.md
Normal file
27
angular/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# MeetingScheduleTemplate
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.31.
|
||||
|
||||
## Development server
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
205
angular/angular.json
Normal file
205
angular/angular.json
Normal file
@ -0,0 +1,205 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"MeetingSchedule": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
{
|
||||
"glob": "abp.signalr.js",
|
||||
"input": "node_modules/abp-web-resources/Abp/Framework/scripts/libs",
|
||||
"output": "/assets/abp"
|
||||
},
|
||||
{
|
||||
"glob": "abp.signalr-client.js",
|
||||
"input": "node_modules/abp-web-resources/Abp/Framework/scripts/libs",
|
||||
"output": "/assets/abp"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/famfamfam-flags/dist/sprite/famfamfam-flags.css",
|
||||
"node_modules/sweetalert2/dist/sweetalert2.css",
|
||||
"src/assets/freeze-ui/freeze-ui.css",
|
||||
"node_modules/@fortawesome/fontawesome-free/css/all.min.css",
|
||||
"node_modules/admin-lte-css-only/css/adminlte.min.css",
|
||||
"src/shared/core.less"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/moment/min/moment.min.js",
|
||||
"node_modules/@aspnet/signalr/dist/browser/signalr.min.js",
|
||||
"node_modules/sweetalert2/dist/sweetalert2.js",
|
||||
"src/assets/freeze-ui/freeze-ui.js",
|
||||
"node_modules/push.js/bin/push.min.js",
|
||||
"node_modules/abp-web-resources/Abp/Framework/scripts/abp.js",
|
||||
"src/assets/abp-web-resources/abp.sweet-alert.js",
|
||||
"src/assets/abp-web-resources/abp.freeze-ui.js",
|
||||
"node_modules/abp-web-resources/Abp/Framework/scripts/libs/abp.moment.js"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hmr": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.hmr.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": ""
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "MeetingSchedule:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "MeetingSchedule:build:production"
|
||||
},
|
||||
"hmr": {
|
||||
"browserTarget": "MeetingSchedule:build:hmr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "MeetingSchedule:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
{
|
||||
"glob": "abp.signalr.js",
|
||||
"input": "node_modules/abp-web-resources/Abp/Framework/scripts/libs",
|
||||
"output": "/assets/abp"
|
||||
},
|
||||
{
|
||||
"glob": "abp.signalr-client.js",
|
||||
"input": "node_modules/abp-web-resources/Abp/Framework/scripts/libs",
|
||||
"output": "/assets/abp"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/famfamfam-flags/dist/sprite/famfamfam-flags.css",
|
||||
"node_modules/sweetalert2/dist/sweetalert2.css",
|
||||
"src/assets/freeze-ui/freeze-ui.css",
|
||||
"node_modules/@fortawesome/fontawesome-free/css/all.min.css",
|
||||
"node_modules/admin-lte-css-only/css/adminlte.min.css",
|
||||
"src/shared/core.less"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/moment/min/moment.min.js",
|
||||
"node_modules/@aspnet/signalr/dist/browser/signalr.min.js",
|
||||
"node_modules/sweetalert2/dist/sweetalert2.js",
|
||||
"src/assets/freeze-ui/freeze-ui.js",
|
||||
"node_modules/push.js/bin/push.min.js",
|
||||
"node_modules/abp-web-resources/Abp/Framework/scripts/abp.js",
|
||||
"src/assets/abp-web-resources/abp.sweet-alert.js",
|
||||
"src/assets/abp-web-resources/abp.freeze-ui.js",
|
||||
"node_modules/abp-web-resources/Abp/Framework/scripts/libs/abp.moment.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.json"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"MeetingSchedule-e2e": {
|
||||
"root": "",
|
||||
"sourceRoot": "",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "MeetingSchedule:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "MeetingSchedule",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"prefix": "app",
|
||||
"style": "css"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"prefix": "app"
|
||||
}
|
||||
}
|
||||
}
|
5
angular/app.config
Normal file
5
angular/app.config
Normal file
@ -0,0 +1,5 @@
|
||||
<configuration>
|
||||
<runtime>
|
||||
<gcServer enabled="true"/>
|
||||
</runtime>
|
||||
</configuration>
|
14
angular/e2e/app.e2e-spec.ts
Normal file
14
angular/e2e/app.e2e-spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { MeetingScheduleTemplatePage } from './app.po';
|
||||
|
||||
describe('MeetingSchedule App', function() {
|
||||
let page: MeetingScheduleTemplatePage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new MeetingScheduleTemplatePage();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('app works!');
|
||||
});
|
||||
});
|
11
angular/e2e/app.po.ts
Normal file
11
angular/e2e/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
export class MeetingScheduleTemplatePage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
29
angular/e2e/tsconfig.json
Normal file
29
angular/e2e/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../dist/out-tsc-e2e",
|
||||
"sourceMap": true,
|
||||
"target": "es6",
|
||||
"typeRoots": [
|
||||
"../node_modules/@types"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@abp/*": [ "../node_modules/abp-ng2-module/dist/src/*" ],
|
||||
"@app/*": [ "./app/*" ],
|
||||
"@shared/*": [ "./shared/*" ],
|
||||
"@node_modules/*": [ "../node_modules/*" ]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"../src/polyfills.ts",
|
||||
"../src/test.ts"
|
||||
],
|
||||
}
|
41
angular/karma.conf.js
Normal file
41
angular/karma.conf.js
Normal file
@ -0,0 +1,41 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
files: [
|
||||
|
||||
],
|
||||
preprocessors: {
|
||||
|
||||
},
|
||||
mime: {
|
||||
'text/x-typescript': ['ts','tsx']
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
config: './.angular-cli.json',
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
||||
? ['progress', 'coverage-istanbul']
|
||||
: ['progress'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
1
angular/nswag/refresh.bat
Normal file
1
angular/nswag/refresh.bat
Normal file
@ -0,0 +1 @@
|
||||
"..\node_modules\.bin\nswag" run
|
209
angular/nswag/service.config.nswag
Normal file
209
angular/nswag/service.config.nswag
Normal file
@ -0,0 +1,209 @@
|
||||
{
|
||||
"runtime": "Default",
|
||||
"defaultVariables": null,
|
||||
"documentGenerator": {
|
||||
"fromDocument": {
|
||||
"url": "https://localhost:44311/swagger/v1/swagger.json",
|
||||
"output": null
|
||||
}
|
||||
},
|
||||
"codeGenerators": {
|
||||
"openApiToTypeScriptClient": {
|
||||
"className": "{controller}ServiceProxy",
|
||||
"moduleName": "",
|
||||
"namespace": "",
|
||||
"typeScriptVersion": 2.0,
|
||||
"template": "Angular",
|
||||
"promiseType": "Promise",
|
||||
"httpClass": "HttpClient",
|
||||
"useSingletonProvider": false,
|
||||
"injectionTokenType": "InjectionToken",
|
||||
"rxJsVersion": 6.0,
|
||||
"dateTimeType": "MomentJS",
|
||||
"nullValue": "Undefined",
|
||||
"generateClientClasses": true,
|
||||
"generateClientInterfaces": false,
|
||||
"generateOptionalParameters": false,
|
||||
"exportTypes": true,
|
||||
"wrapDtoExceptions": false,
|
||||
"exceptionClass": "ApiException",
|
||||
"clientBaseClass": null,
|
||||
"wrapResponses": false,
|
||||
"wrapResponseMethods": [],
|
||||
"generateResponseClasses": true,
|
||||
"responseClass": "SwaggerResponse",
|
||||
"protectedMethods": [],
|
||||
"configurationClass": null,
|
||||
"useTransformOptionsMethod": false,
|
||||
"useTransformResultMethod": false,
|
||||
"generateDtoTypes": true,
|
||||
"operationGenerationMode": "MultipleClientsFromPathSegments",
|
||||
"markOptionalProperties": false,
|
||||
"generateCloneMethod": true,
|
||||
"typeStyle": "Class",
|
||||
"classTypes": [],
|
||||
"extendedClasses": [],
|
||||
"extensionCode": null,
|
||||
"generateDefaultValues": true,
|
||||
"excludedTypeNames": [],
|
||||
"excludedParameterNames": [],
|
||||
"handleReferences": false,
|
||||
"generateConstructorInterface": true,
|
||||
"convertConstructorInterfaceData": false,
|
||||
"importRequiredTypes": true,
|
||||
"useGetBaseUrlMethod": false,
|
||||
"baseUrlTokenName": "API_BASE_URL",
|
||||
"queryNullValue": "",
|
||||
"inlineNamedDictionaries": false,
|
||||
"inlineNamedAny": false,
|
||||
"templateDirectory": null,
|
||||
"typeNameGeneratorType": null,
|
||||
"propertyNameGeneratorType": null,
|
||||
"enumNameGeneratorType": null,
|
||||
"serviceHost": null,
|
||||
"serviceSchemes": null,
|
||||
"output": "../src/shared/service-proxies/service-proxies.ts"
|
||||
},
|
||||
"openApiToCSharpClient": {
|
||||
"clientBaseClass": null,
|
||||
"configurationClass": null,
|
||||
"generateClientClasses": true,
|
||||
"generateClientInterfaces": false,
|
||||
"injectHttpClient": false,
|
||||
"disposeHttpClient": true,
|
||||
"protectedMethods": [],
|
||||
"generateExceptionClasses": true,
|
||||
"exceptionClass": "SwaggerException",
|
||||
"wrapDtoExceptions": true,
|
||||
"useHttpClientCreationMethod": false,
|
||||
"httpClientType": "System.Net.Http.HttpClient",
|
||||
"useHttpRequestMessageCreationMethod": false,
|
||||
"useBaseUrl": true,
|
||||
"generateBaseUrlProperty": true,
|
||||
"generateSyncMethods": false,
|
||||
"exposeJsonSerializerSettings": false,
|
||||
"clientClassAccessModifier": "public",
|
||||
"typeAccessModifier": "public",
|
||||
"generateContractsOutput": false,
|
||||
"contractsNamespace": null,
|
||||
"contractsOutputFilePath": null,
|
||||
"parameterDateTimeFormat": "s",
|
||||
"generateUpdateJsonSerializerSettingsMethod": true,
|
||||
"serializeTypeInformation": false,
|
||||
"queryNullValue": "",
|
||||
"className": "{controller}Client",
|
||||
"operationGenerationMode": "MultipleClientsFromOperationId",
|
||||
"additionalNamespaceUsages": [],
|
||||
"additionalContractNamespaceUsages": [],
|
||||
"generateOptionalParameters": false,
|
||||
"generateJsonMethods": true,
|
||||
"enforceFlagEnums": false,
|
||||
"parameterArrayType": "System.Collections.Generic.IEnumerable",
|
||||
"parameterDictionaryType": "System.Collections.Generic.IDictionary",
|
||||
"responseArrayType": "System.Collections.ObjectModel.ObservableCollection",
|
||||
"responseDictionaryType": "System.Collections.Generic.Dictionary",
|
||||
"wrapResponses": false,
|
||||
"wrapResponseMethods": [],
|
||||
"generateResponseClasses": true,
|
||||
"responseClass": "SwaggerResponse",
|
||||
"namespace": "MyNamespace",
|
||||
"requiredPropertiesMustBeDefined": true,
|
||||
"dateType": "System.DateTime",
|
||||
"jsonConverters": null,
|
||||
"anyType": "object",
|
||||
"dateTimeType": "System.DateTime",
|
||||
"timeType": "System.TimeSpan",
|
||||
"timeSpanType": "System.TimeSpan",
|
||||
"arrayType": "System.Collections.ObjectModel.ObservableCollection",
|
||||
"arrayInstanceType": "System.Collections.ObjectModel.Collection",
|
||||
"dictionaryType": "System.Collections.Generic.Dictionary",
|
||||
"dictionaryInstanceType": "System.Collections.Generic.Dictionary",
|
||||
"arrayBaseType": "System.Collections.ObjectModel.ObservableCollection",
|
||||
"dictionaryBaseType": "System.Collections.Generic.Dictionary",
|
||||
"classStyle": "Inpc",
|
||||
"generateDefaultValues": true,
|
||||
"generateDataAnnotations": true,
|
||||
"excludedTypeNames": [],
|
||||
"excludedParameterNames": [],
|
||||
"handleReferences": false,
|
||||
"generateImmutableArrayProperties": false,
|
||||
"generateImmutableDictionaryProperties": false,
|
||||
"jsonSerializerSettingsTransformationMethod": null,
|
||||
"inlineNamedArrays": false,
|
||||
"inlineNamedDictionaries": false,
|
||||
"inlineNamedTuples": true,
|
||||
"inlineNamedAny": false,
|
||||
"generateDtoTypes": true,
|
||||
"generateOptionalPropertiesAsNullable": false,
|
||||
"templateDirectory": null,
|
||||
"typeNameGeneratorType": null,
|
||||
"propertyNameGeneratorType": null,
|
||||
"enumNameGeneratorType": null,
|
||||
"serviceHost": null,
|
||||
"serviceSchemes": null,
|
||||
"output": null
|
||||
},
|
||||
"openApiToCSharpController": {
|
||||
"controllerBaseClass": null,
|
||||
"controllerStyle": "Partial",
|
||||
"controllerTarget": "AspNet",
|
||||
"useCancellationToken": false,
|
||||
"useActionResultType": false,
|
||||
"generateModelValidationAttributes": false,
|
||||
"routeNamingStrategy": "None",
|
||||
"className": "{controller}",
|
||||
"operationGenerationMode": "MultipleClientsFromOperationId",
|
||||
"additionalNamespaceUsages": [
|
||||
"System.Web.Http"
|
||||
],
|
||||
"additionalContractNamespaceUsages": [],
|
||||
"generateOptionalParameters": false,
|
||||
"generateJsonMethods": true,
|
||||
"enforceFlagEnums": false,
|
||||
"parameterArrayType": "System.Collections.Generic.IEnumerable",
|
||||
"parameterDictionaryType": "System.Collections.Generic.IDictionary",
|
||||
"responseArrayType": "System.Collections.ObjectModel.ObservableCollection",
|
||||
"responseDictionaryType": "System.Collections.Generic.Dictionary",
|
||||
"wrapResponses": false,
|
||||
"wrapResponseMethods": [],
|
||||
"generateResponseClasses": true,
|
||||
"responseClass": "SwaggerResponse",
|
||||
"namespace": "MyNamespace",
|
||||
"requiredPropertiesMustBeDefined": true,
|
||||
"dateType": "System.DateTime",
|
||||
"jsonConverters": null,
|
||||
"anyType": "object",
|
||||
"dateTimeType": "System.DateTime",
|
||||
"timeType": "System.TimeSpan",
|
||||
"timeSpanType": "System.TimeSpan",
|
||||
"arrayType": "System.Collections.Generic.IEnumerable",
|
||||
"arrayInstanceType": "System.Collections.ObjectModel.Collection",
|
||||
"dictionaryType": "System.Collections.Generic.Dictionary",
|
||||
"dictionaryInstanceType": "System.Collections.Generic.Dictionary",
|
||||
"arrayBaseType": "System.Collections.ObjectModel.ObservableCollection",
|
||||
"dictionaryBaseType": "System.Collections.Generic.Dictionary",
|
||||
"classStyle": "Inpc",
|
||||
"generateDefaultValues": true,
|
||||
"generateDataAnnotations": true,
|
||||
"excludedTypeNames": [],
|
||||
"excludedParameterNames": [],
|
||||
"handleReferences": false,
|
||||
"generateImmutableArrayProperties": false,
|
||||
"generateImmutableDictionaryProperties": false,
|
||||
"jsonSerializerSettingsTransformationMethod": null,
|
||||
"inlineNamedArrays": false,
|
||||
"inlineNamedDictionaries": false,
|
||||
"inlineNamedTuples": true,
|
||||
"inlineNamedAny": false,
|
||||
"generateDtoTypes": true,
|
||||
"generateOptionalPropertiesAsNullable": false,
|
||||
"templateDirectory": null,
|
||||
"typeNameGeneratorType": null,
|
||||
"propertyNameGeneratorType": null,
|
||||
"enumNameGeneratorType": null,
|
||||
"serviceHost": null,
|
||||
"serviceSchemes": null,
|
||||
"output": null
|
||||
}
|
||||
}
|
||||
}
|
70
angular/package.json
Normal file
70
angular/package.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "MeetingSchedule",
|
||||
"version": "4.7.1",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"pree2e": "webdriver-manager update --standalone false --gecko false",
|
||||
"e2e": "protractor",
|
||||
"hmr": "ng serve --host 0.0.0.0 --port 4200 --hmr",
|
||||
"lint": "tslint --force --project src/tsconfig.json src/**/*.ts -t verbose",
|
||||
"ng": "ng",
|
||||
"start": "ng serve --host 0.0.0.0 --port 4200",
|
||||
"test": "ng test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^12.0.5",
|
||||
"@angular/common": "^12.0.5",
|
||||
"@angular/compiler": "^12.0.5",
|
||||
"@angular/core": "^12.0.5",
|
||||
"@angular/forms": "^12.0.5",
|
||||
"@angular/platform-browser": "^12.0.5",
|
||||
"@angular/platform-browser-dynamic": "^12.0.5",
|
||||
"@angular/router": "^12.0.5",
|
||||
"@aspnet/signalr": "^1.1.4",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"abp-ng2-module": "^6.2.0",
|
||||
"abp-web-resources": "^5.3.0",
|
||||
"admin-lte-css-only": "^3.1.0",
|
||||
"core-js": "^3.2.1",
|
||||
"famfamfam-flags": "^1.0.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"moment": "2.24.0",
|
||||
"moment-timezone": "0.5.33",
|
||||
"ngx-bootstrap": "^5.6.1",
|
||||
"ngx-pagination": "^5.0.0",
|
||||
"push.js": "1.0.12",
|
||||
"rxjs": "^6.4.0",
|
||||
"sweetalert2": "^10.15.6",
|
||||
"ts-helpers": "^1.1.2",
|
||||
"tslib": "^2.0.0",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~12.0.5",
|
||||
"@angular/cli": "^12.0.5",
|
||||
"@angular/compiler-cli": "^12.0.5",
|
||||
"@angularclass/hmr": "^2.1.3",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@types/moment-timezone": "^0.5.30",
|
||||
"@types/node": "^13.13.4",
|
||||
"codelyzer": "^6.0.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~6.3.4",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"nswag": "^13.4.2",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "^8.10.1",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "4.2.4",
|
||||
"webpack-bundle-analyzer": "^3.7.0"
|
||||
},
|
||||
"angular-cli": {}
|
||||
}
|
32
angular/protractor.conf.js
Normal file
32
angular/protractor.conf.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
/*global jasmine */
|
||||
var SpecReporter = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
useAllAngular2AppRoots: true,
|
||||
beforeLaunch: function() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e'
|
||||
});
|
||||
},
|
||||
onPrepare: function() {
|
||||
jasmine.getEnv().addReporter(new SpecReporter());
|
||||
}
|
||||
};
|
24
angular/src/account/account-routing.module.ts
Normal file
24
angular/src/account/account-routing.module.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { AccountComponent } from './account.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: AccountComponent,
|
||||
children: [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent }
|
||||
]
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class AccountRoutingModule { }
|
15
angular/src/account/account.component.html
Normal file
15
angular/src/account/account.component.html
Normal file
@ -0,0 +1,15 @@
|
||||
<div class="login-box">
|
||||
<account-header></account-header>
|
||||
<div class="card">
|
||||
<div *ngIf="showTenantChange()" class="card-header">
|
||||
<tenant-change></tenant-change>
|
||||
</div>
|
||||
<div class="card-body login-card-body">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<account-languages></account-languages>
|
||||
</div>
|
||||
</div>
|
||||
<account-footer></account-footer>
|
||||
</div>
|
26
angular/src/account/account.component.ts
Normal file
26
angular/src/account/account.component.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ViewEncapsulation,
|
||||
Injector,
|
||||
Renderer2
|
||||
} from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
|
||||
@Component({
|
||||
templateUrl: './account.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AccountComponent extends AppComponentBase implements OnInit {
|
||||
constructor(injector: Injector, private renderer: Renderer2) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
showTenantChange(): boolean {
|
||||
return abp.multiTenancy.isEnabled;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.renderer.addClass(document.body, 'login-page');
|
||||
}
|
||||
}
|
50
angular/src/account/account.module.ts
Normal file
50
angular/src/account/account.module.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientJsonpModule } from '@angular/common/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { ModalModule } from 'ngx-bootstrap/modal';
|
||||
import { AccountRoutingModule } from './account-routing.module';
|
||||
import { ServiceProxyModule } from '@shared/service-proxies/service-proxy.module';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { AccountComponent } from './account.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { AccountLanguagesComponent } from './layout/account-languages.component';
|
||||
import { AccountHeaderComponent } from './layout/account-header.component';
|
||||
import { AccountFooterComponent } from './layout/account-footer.component';
|
||||
|
||||
// tenants
|
||||
import { TenantChangeComponent } from './tenant/tenant-change.component';
|
||||
import { TenantChangeDialogComponent } from './tenant/tenant-change-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
HttpClientJsonpModule,
|
||||
SharedModule,
|
||||
ServiceProxyModule,
|
||||
AccountRoutingModule,
|
||||
ModalModule.forChild()
|
||||
],
|
||||
declarations: [
|
||||
AccountComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
AccountLanguagesComponent,
|
||||
AccountHeaderComponent,
|
||||
AccountFooterComponent,
|
||||
// tenant
|
||||
TenantChangeComponent,
|
||||
TenantChangeDialogComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
// tenant
|
||||
TenantChangeDialogComponent
|
||||
]
|
||||
})
|
||||
export class AccountModule {
|
||||
|
||||
}
|
9
angular/src/account/layout/account-footer.component.html
Normal file
9
angular/src/account/layout/account-footer.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<small>
|
||||
Copyright © {{ currentYear }}
|
||||
<b class="ml-2">{{ "Version" | localize }}</b>
|
||||
{{ versionText }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
23
angular/src/account/layout/account-footer.component.ts
Normal file
23
angular/src/account/layout/account-footer.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Component, Injector, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
|
||||
@Component({
|
||||
selector: 'account-footer',
|
||||
templateUrl: './account-footer.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AccountFooterComponent extends AppComponentBase {
|
||||
currentYear: number;
|
||||
versionText: string;
|
||||
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
|
||||
this.currentYear = new Date().getFullYear();
|
||||
this.versionText =
|
||||
this.appSession.application.version +
|
||||
' [' +
|
||||
this.appSession.application.releaseDate.format('YYYYDDMM') +
|
||||
']';
|
||||
}
|
||||
}
|
3
angular/src/account/layout/account-header.component.html
Normal file
3
angular/src/account/layout/account-header.component.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div class="login-logo">
|
||||
<a href="/"><b>MeetingSchedule</b></a>
|
||||
</div>
|
8
angular/src/account/layout/account-header.component.ts
Normal file
8
angular/src/account/layout/account-header.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'account-header',
|
||||
templateUrl: './account-header.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AccountHeaderComponent {}
|
18
angular/src/account/layout/account-languages.component.html
Normal file
18
angular/src/account/layout/account-languages.component.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="text-center">
|
||||
<ng-container *ngFor="let language of languages">
|
||||
<a
|
||||
*ngIf="language.name != currentLanguage.name"
|
||||
href="javascript:void(0);"
|
||||
(click)="changeLanguage(language.name)"
|
||||
>
|
||||
<span
|
||||
title="{{ language.displayName }}"
|
||||
[attr.class.current-language-icon]="
|
||||
language.name != currentLanguage.name
|
||||
"
|
||||
>
|
||||
<i class="d-inline-block mx-1 {{ language.icon }}"></i>
|
||||
</span>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
42
angular/src/account/layout/account-languages.component.ts
Normal file
42
angular/src/account/layout/account-languages.component.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Injector,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { filter as _filter } from 'lodash-es';
|
||||
|
||||
@Component({
|
||||
selector: 'account-languages',
|
||||
templateUrl: './account-languages.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AccountLanguagesComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
languages: abp.localization.ILanguageInfo[];
|
||||
currentLanguage: abp.localization.ILanguageInfo;
|
||||
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.languages = _filter(
|
||||
this.localization.languages,
|
||||
(l) => !l.isDisabled
|
||||
);
|
||||
this.currentLanguage = this.localization.currentLanguage;
|
||||
}
|
||||
|
||||
changeLanguage(languageName: string): void {
|
||||
abp.utils.setCookieValue(
|
||||
'Abp.Localization.CultureName',
|
||||
languageName,
|
||||
new Date(new Date().getTime() + 5 * 365 * 86400000), // 5 year
|
||||
abp.appPath
|
||||
);
|
||||
|
||||
location.reload();
|
||||
}
|
||||
}
|
83
angular/src/account/login/login.component.html
Normal file
83
angular/src/account/login/login.component.html
Normal file
@ -0,0 +1,83 @@
|
||||
<div [@routerTransition]>
|
||||
<h4 class="text-center mb-3">{{ "LogIn" | localize }}</h4>
|
||||
<form novalidate autocomplete="off" #loginForm="ngForm" (ngSubmit)="login()">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="userNameOrEmailAddress"
|
||||
[(ngModel)]="authService.authenticateModel.userNameOrEmailAddress"
|
||||
[placeholder]="'UserNameOrEmail' | localize"
|
||||
required
|
||||
maxlength="256"
|
||||
#userNameOrEmailAddressModel="ngModel"
|
||||
#userNameOrEmailAddressEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="userNameOrEmailAddressModel"
|
||||
[controlEl]="userNameOrEmailAddressEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
[(ngModel)]="authService.authenticateModel.password"
|
||||
[placeholder]="'Password' | localize"
|
||||
required
|
||||
maxlength="32"
|
||||
#passwordModel="ngModel"
|
||||
#passwordEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="passwordModel"
|
||||
[controlEl]="passwordEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-8">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
id="rememberMe"
|
||||
name="rememberMe"
|
||||
[(ngModel)]="authService.rememberMe"
|
||||
/>
|
||||
<label for="rememberMe" class="custom-control-label">
|
||||
{{ "RememberMe" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block"
|
||||
[disabled]="!loginForm.form.valid || submitting"
|
||||
>
|
||||
{{ "LogIn" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p *ngIf="isSelfRegistrationAllowed" class="mb-1">
|
||||
<a [routerLink]="['../register']">
|
||||
<i class="fa fa-plus-circle"></i> {{ "Register" | localize }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
38
angular/src/account/login/login.component.ts
Normal file
38
angular/src/account/login/login.component.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { AbpSessionService } from 'abp-ng2-module';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { accountModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import { AppAuthService } from '@shared/auth/app-auth.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './login.component.html',
|
||||
animations: [accountModuleAnimation()]
|
||||
})
|
||||
export class LoginComponent extends AppComponentBase {
|
||||
submitting = false;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
public authService: AppAuthService,
|
||||
private _sessionService: AbpSessionService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
get multiTenancySideIsTeanant(): boolean {
|
||||
return this._sessionService.tenantId > 0;
|
||||
}
|
||||
|
||||
get isSelfRegistrationAllowed(): boolean {
|
||||
if (!this._sessionService.tenantId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
login(): void {
|
||||
this.submitting = true;
|
||||
this.authService.authenticate(() => (this.submitting = false));
|
||||
}
|
||||
}
|
147
angular/src/account/register/register.component.html
Normal file
147
angular/src/account/register/register.component.html
Normal file
@ -0,0 +1,147 @@
|
||||
<div [@routerTransition]>
|
||||
<h4 class="text-center mb-3">{{ "Register" | localize }}</h4>
|
||||
<form autocomplete="off" #registerForm="ngForm" (ngSubmit)="save()">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
placeholder="{{ 'Name' | localize }}"
|
||||
required
|
||||
maxlength="64"
|
||||
[(ngModel)]="model.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-arrow-left"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="surname"
|
||||
placeholder="{{ 'Surname' | localize }}"
|
||||
required
|
||||
maxlength="64"
|
||||
[(ngModel)]="model.surname"
|
||||
#surnameModel="ngModel"
|
||||
#surnameEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-arrow-left"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="surnameModel"
|
||||
[controlEl]="surnameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
name="emailAddress"
|
||||
placeholder="{{ 'EmailAddress' | localize }}"
|
||||
required
|
||||
maxlength="256"
|
||||
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$"
|
||||
[(ngModel)]="model.emailAddress"
|
||||
#emailAddressModel="ngModel"
|
||||
#emailAddressEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-envelope"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="emailAddressModel"
|
||||
[controlEl]="emailAddressEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
name="userName"
|
||||
placeholder=" {{ 'UserName' | localize }}"
|
||||
required
|
||||
maxlength="32"
|
||||
[(ngModel)]="model.userName"
|
||||
#userNameModel="ngModel"
|
||||
#userNameEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="userNameModel"
|
||||
[controlEl]="userNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="{{ 'Password' | localize }}"
|
||||
[(ngModel)]="model.password"
|
||||
required
|
||||
maxlength="32"
|
||||
#passwordModel="ngModel"
|
||||
#passwordEl
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-validation-summary
|
||||
[control]="passwordModel"
|
||||
[controlEl]="passwordEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
[disabled]="saving"
|
||||
[routerLink]="['../login']"
|
||||
>
|
||||
<i class="fa fa-arrow-circle-left"></i> {{ "Back" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block"
|
||||
[disabled]="!registerForm.form.valid || saving"
|
||||
>
|
||||
{{ "Register" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
55
angular/src/account/register/register.component.ts
Normal file
55
angular/src/account/register/register.component.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
AccountServiceProxy,
|
||||
RegisterInput,
|
||||
RegisterOutput
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { accountModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import { AppAuthService } from '@shared/auth/app-auth.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './register.component.html',
|
||||
animations: [accountModuleAnimation()]
|
||||
})
|
||||
export class RegisterComponent extends AppComponentBase {
|
||||
model: RegisterInput = new RegisterInput();
|
||||
saving = false;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _accountService: AccountServiceProxy,
|
||||
private _router: Router,
|
||||
private authService: AppAuthService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
this._accountService
|
||||
.register(this.model)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.saving = false;
|
||||
})
|
||||
)
|
||||
.subscribe((result: RegisterOutput) => {
|
||||
if (!result.canLogin) {
|
||||
this.notify.success(this.l('SuccessfullyRegistered'));
|
||||
this._router.navigate(['/login']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Autheticate
|
||||
this.saving = true;
|
||||
this.authService.authenticateModel.userNameOrEmailAddress = this.model.userName;
|
||||
this.authService.authenticateModel.password = this.model.password;
|
||||
this.authService.authenticate(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#changeTenantForm="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'ChangeTenant' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="tenancyName">
|
||||
{{ "TenancyName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="tenancyName"
|
||||
name="tenancyName"
|
||||
[(ngModel)]="tenancyName"
|
||||
maxlength="64"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
<i class="fa fa-info-circle"></i>
|
||||
{{ "LeaveEmptyToSwitchToHost" | localize }}
|
||||
</span>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!changeTenantForm.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
60
angular/src/account/tenant/tenant-change-dialog.component.ts
Normal file
60
angular/src/account/tenant/tenant-change-dialog.component.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { AccountServiceProxy } from '@shared/service-proxies/service-proxies';
|
||||
import { AppTenantAvailabilityState } from '@shared/AppEnums';
|
||||
import {
|
||||
IsTenantAvailableInput,
|
||||
IsTenantAvailableOutput
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
|
||||
@Component({
|
||||
templateUrl: './tenant-change-dialog.component.html'
|
||||
})
|
||||
export class TenantChangeDialogComponent extends AppComponentBase {
|
||||
saving = false;
|
||||
tenancyName = '';
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _accountService: AccountServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (!this.tenancyName) {
|
||||
abp.multiTenancy.setTenantIdCookie(undefined);
|
||||
this.bsModalRef.hide();
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const input = new IsTenantAvailableInput();
|
||||
input.tenancyName = this.tenancyName;
|
||||
|
||||
this.saving = true;
|
||||
this._accountService.isTenantAvailable(input).subscribe(
|
||||
(result: IsTenantAvailableOutput) => {
|
||||
switch (result.state) {
|
||||
case AppTenantAvailabilityState.Available:
|
||||
abp.multiTenancy.setTenantIdCookie(result.tenantId);
|
||||
location.reload();
|
||||
return;
|
||||
case AppTenantAvailabilityState.InActive:
|
||||
this.message.warn(this.l('TenantIsNotActive', this.tenancyName));
|
||||
break;
|
||||
case AppTenantAvailabilityState.NotFound:
|
||||
this.message.warn(
|
||||
this.l('ThereIsNoTenantDefinedWithName{0}', this.tenancyName)
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
12
angular/src/account/tenant/tenant-change.component.html
Normal file
12
angular/src/account/tenant/tenant-change.component.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div *ngIf="isMultiTenancyEnabled" class="text-center tenant-change-component">
|
||||
<span>
|
||||
{{ "CurrentTenant" | localize }}:
|
||||
<span *ngIf="tenancyName" title="{{ name }}">
|
||||
<strong>{{ tenancyName }}</strong>
|
||||
</span>
|
||||
<span *ngIf="!tenancyName">{{ "NotSelected" | localize }}</span>
|
||||
(<a href="javascript:;" (click)="showChangeModal()">
|
||||
{{ "Change" | localize }} </a
|
||||
>)
|
||||
</span>
|
||||
</div>
|
35
angular/src/account/tenant/tenant-change.component.ts
Normal file
35
angular/src/account/tenant/tenant-change.component.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { TenantChangeDialogComponent } from './tenant-change-dialog.component';
|
||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'tenant-change',
|
||||
templateUrl: './tenant-change.component.html'
|
||||
})
|
||||
export class TenantChangeComponent extends AppComponentBase implements OnInit {
|
||||
tenancyName = '';
|
||||
name = '';
|
||||
|
||||
constructor(injector: Injector, private _modalService: BsModalService) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
get isMultiTenancyEnabled(): boolean {
|
||||
return abp.multiTenancy.isEnabled;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.appSession.tenant) {
|
||||
this.tenancyName = this.appSession.tenant.tenancyName;
|
||||
this.name = this.appSession.tenant.name;
|
||||
}
|
||||
}
|
||||
|
||||
showChangeModal(): void {
|
||||
const modal = this._modalService.show(TenantChangeDialogComponent);
|
||||
if (this.appSession.tenant) {
|
||||
modal.content.tenancyName = this.appSession.tenant.tenancyName;
|
||||
}
|
||||
}
|
||||
}
|
170
angular/src/app-initializer.ts
Normal file
170
angular/src/app-initializer.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { PlatformLocation, registerLocaleData } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import * as moment from 'moment-timezone';
|
||||
import { filter as _filter, merge as _merge } from 'lodash-es';
|
||||
import { AppConsts } from '@shared/AppConsts';
|
||||
import { AppSessionService } from '@shared/session/app-session.service';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AppInitializer {
|
||||
constructor(
|
||||
private _injector: Injector,
|
||||
private _platformLocation: PlatformLocation,
|
||||
private _httpClient: HttpClient
|
||||
) { }
|
||||
|
||||
init(): () => Promise<boolean> {
|
||||
return () => {
|
||||
abp.ui.setBusy();
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
AppConsts.appBaseHref = this.getBaseHref();
|
||||
const appBaseUrl = this.getDocumentOrigin() + AppConsts.appBaseHref;
|
||||
this.getApplicationConfig(appBaseUrl, () => {
|
||||
this.getUserConfiguration(() => {
|
||||
abp.event.trigger('abp.dynamicScriptsInitialized');
|
||||
// do not use constructor injection for AppSessionService
|
||||
const appSessionService = this._injector.get(AppSessionService);
|
||||
appSessionService.init().then(
|
||||
(result) => {
|
||||
abp.ui.clearBusy();
|
||||
if (this.shouldLoadLocale()) {
|
||||
const angularLocale = this.convertAbpLocaleToAngularLocale(
|
||||
abp.localization.currentLanguage.name
|
||||
);
|
||||
import(`@angular/common/locales/${angularLocale}.js`).then(
|
||||
(module) => {
|
||||
registerLocaleData(module.default);
|
||||
resolve(result);
|
||||
},
|
||||
reject
|
||||
);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
abp.ui.clearBusy();
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private getBaseHref(): string {
|
||||
const baseUrl = this._platformLocation.getBaseHrefFromDOM();
|
||||
if (baseUrl) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
return '/';
|
||||
}
|
||||
|
||||
private getDocumentOrigin(): string {
|
||||
if (!document.location.origin) {
|
||||
const port = document.location.port ? ':' + document.location.port : '';
|
||||
return (
|
||||
document.location.protocol + '//' + document.location.hostname + port
|
||||
);
|
||||
}
|
||||
|
||||
return document.location.origin;
|
||||
}
|
||||
|
||||
private shouldLoadLocale(): boolean {
|
||||
return (
|
||||
abp.localization.currentLanguage.name &&
|
||||
abp.localization.currentLanguage.name !== 'en-US'
|
||||
);
|
||||
}
|
||||
|
||||
private convertAbpLocaleToAngularLocale(locale: string): string {
|
||||
if (!AppConsts.localeMappings) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
const localeMapings = _filter(AppConsts.localeMappings, { from: locale });
|
||||
if (localeMapings && localeMapings.length) {
|
||||
return localeMapings[0]['to'];
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
private getCurrentClockProvider(
|
||||
currentProviderName: string
|
||||
): abp.timing.IClockProvider {
|
||||
if (currentProviderName === 'unspecifiedClockProvider') {
|
||||
return abp.timing.unspecifiedClockProvider;
|
||||
}
|
||||
|
||||
if (currentProviderName === 'utcClockProvider') {
|
||||
return abp.timing.utcClockProvider;
|
||||
}
|
||||
|
||||
return abp.timing.localClockProvider;
|
||||
}
|
||||
|
||||
private getUserConfiguration(callback: () => void): void {
|
||||
const cookieLangValue = abp.utils.getCookieValue(
|
||||
'Abp.Localization.CultureName'
|
||||
);
|
||||
const token = abp.auth.getToken();
|
||||
|
||||
const requestHeaders = {
|
||||
'Abp.TenantId': `${abp.multiTenancy.getTenantIdCookie()}`,
|
||||
'.AspNetCore.Culture': `c=${cookieLangValue}|uic=${cookieLangValue}`,
|
||||
};
|
||||
|
||||
if (token) {
|
||||
requestHeaders['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
this._httpClient
|
||||
.get<any>(
|
||||
`${AppConsts.remoteServiceBaseUrl}/AbpUserConfiguration/GetAll`,
|
||||
{
|
||||
headers: requestHeaders,
|
||||
}
|
||||
)
|
||||
.subscribe((response) => {
|
||||
const result = response.result;
|
||||
|
||||
_merge(abp, result);
|
||||
|
||||
abp.clock.provider = this.getCurrentClockProvider(
|
||||
result.clock.provider
|
||||
);
|
||||
|
||||
moment.locale(abp.localization.currentLanguage.name);
|
||||
|
||||
if (abp.clock.provider.supportsMultipleTimezone) {
|
||||
moment.tz.setDefault(abp.timing.timeZoneInfo.iana.timeZoneId);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
private getApplicationConfig(appRootUrl: string, callback: () => void) {
|
||||
this._httpClient
|
||||
.get<any>(`${appRootUrl}assets/${environment.appConfig}`, {
|
||||
headers: {
|
||||
'Abp.TenantId': `${abp.multiTenancy.getTenantIdCookie()}`,
|
||||
},
|
||||
})
|
||||
.subscribe((response) => {
|
||||
AppConsts.appBaseUrl = response.appBaseUrl;
|
||||
AppConsts.remoteServiceBaseUrl = response.remoteServiceBaseUrl;
|
||||
AppConsts.localeMappings = response.localeMappings;
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
136
angular/src/app/about/about.component.html
Normal file
136
angular/src/app/about/about.component.html
Normal file
@ -0,0 +1,136 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>{{ "About" | localize }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
This is a simple startup template based on ASP.NET Boilerplate
|
||||
framework and Module Zero. If you need an enterprise startup
|
||||
project, check
|
||||
<a href="http://aspnetzero.com?ref=abptmpl" target="_blank">
|
||||
ASP.NET ZERO </a
|
||||
>.
|
||||
</p>
|
||||
<h3>What is ASP.NET Boilerplate?</h3>
|
||||
<p>
|
||||
ASP.NET Boilerplate is an application framework built on latest
|
||||
<strong>ASP.NET Core</strong> framework. It makes easy to use
|
||||
authorization, dependency injection, validation, exception handling,
|
||||
localization, logging, caching, background jobs and so on. It's
|
||||
built on already familiar tools like Entity Framework, AutoMapper,
|
||||
Castle Windsor...
|
||||
</p>
|
||||
<p>
|
||||
ASP.NET Boilerplate implements
|
||||
<strong>NLayer architecture</strong> (Domain, Application,
|
||||
Infrastructure and Presentation Layers) and
|
||||
<strong>Domain Driven Design</strong> (Entities, Repositories,
|
||||
Domain/Application Services, DTO's...). Also implements and provides
|
||||
a good infrastructure to implement common software development
|
||||
<strong>best practices</strong>.
|
||||
</p>
|
||||
<h3>What is Module Zero?</h3>
|
||||
<p>
|
||||
ASP.NET Boilerplate framework is designed to be independent of any
|
||||
database schema and to be as generic as possible. Therefore, It
|
||||
leaves some concepts
|
||||
<strong>abstract</strong> and <strong>optional</strong> (like audit
|
||||
logging, permission and setting stores) which requires some
|
||||
<strong>data store</strong>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Module Zero </strong>implements all fundamental concepts of
|
||||
ASP.NET Boilerplate framework such as
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/Tenant-Management"
|
||||
>tenant management</a
|
||||
>
|
||||
(<strong>multi-tenancy</strong>),
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/Role-Management"
|
||||
>
|
||||
role management </a
|
||||
>,
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/User-Management"
|
||||
>user management</a
|
||||
>,
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Authorization"
|
||||
>authorization</a
|
||||
>
|
||||
(<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/Permission-Management"
|
||||
>
|
||||
permission management </a
|
||||
>),
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Setting-Management"
|
||||
>setting management</a
|
||||
>,
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/Language-Management"
|
||||
>
|
||||
language management </a
|
||||
>,
|
||||
<a
|
||||
href="http://www.aspnetboilerplate.com/Pages/Documents/Audit-Logging"
|
||||
>audit logging</a
|
||||
>
|
||||
and so on.
|
||||
</p>
|
||||
<p>
|
||||
Module-Zero defines entities and implements
|
||||
<strong>domain logic</strong>
|
||||
(domain layer) and leaves application and presentation layers to
|
||||
you.
|
||||
</p>
|
||||
<h4>Based on Microsoft ASP.NET Core Identity</h4>
|
||||
<p>
|
||||
Module Zero is based on Microsoft's
|
||||
<a
|
||||
href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity"
|
||||
target="_blank"
|
||||
>ASP.NET Core Identity</a
|
||||
>
|
||||
library. It extends user and role managers and implements user and
|
||||
role stores using generic repositories.
|
||||
</p>
|
||||
<h3>Documentation</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template-Core"
|
||||
>Documentation for this template</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.aspnetboilerplate.com/Pages/Documents"
|
||||
>ASP.NET Boilerplate documentation</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Source code</h3>
|
||||
<p>
|
||||
This template is developed open source on Github. You can contribute
|
||||
to the template.
|
||||
<a
|
||||
href="https://github.com/aspnetboilerplate/module-zero-core-template"
|
||||
target="_blank"
|
||||
>https://github.com/aspnetboilerplate/module-zero-core-template</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
14
angular/src/app/about/about.component.ts
Normal file
14
angular/src/app/about/about.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component, Injector, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
|
||||
@Component({
|
||||
templateUrl: './about.component.html',
|
||||
animations: [appModuleAnimation()],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutComponent extends AppComponentBase {
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
}
|
||||
}
|
31
angular/src/app/app-routing.module.ts
Normal file
31
angular/src/app/app-routing.module.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRouteGuard } from '@shared/auth/auth-route-guard';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { AboutComponent } from './about/about.component';
|
||||
import { UsersComponent } from './users/users.component';
|
||||
import { TenantsComponent } from './tenants/tenants.component';
|
||||
import { RolesComponent } from 'app/roles/roles.component';
|
||||
import { ChangePasswordComponent } from './users/change-password/change-password.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: AppComponent,
|
||||
children: [
|
||||
{ path: 'home', component: HomeComponent, canActivate: [AppRouteGuard] },
|
||||
{ path: 'users', component: UsersComponent, data: { permission: 'Pages.Users' }, canActivate: [AppRouteGuard] },
|
||||
{ path: 'roles', component: RolesComponent, data: { permission: 'Pages.Roles' }, canActivate: [AppRouteGuard] },
|
||||
{ path: 'tenants', component: TenantsComponent, data: { permission: 'Pages.Tenants' }, canActivate: [AppRouteGuard] },
|
||||
{ path: 'about', component: AboutComponent, canActivate: [AppRouteGuard] },
|
||||
{ path: 'update-password', component: ChangePasswordComponent, canActivate: [AppRouteGuard] }
|
||||
]
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
9
angular/src/app/app.component.html
Normal file
9
angular/src/app/app.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="wrapper">
|
||||
<app-header></app-header>
|
||||
<sidebar></sidebar>
|
||||
<div class="content-wrapper">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
<div id="sidebar-overlay" (click)="toggleSidebar()"></div>
|
||||
</div>
|
97
angular/src/app/app.component.spec.ts
Normal file
97
angular/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { TestBed, async } from "@angular/core/testing";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { LayoutStoreService } from "../shared/layout/layout-store.service";
|
||||
import { AppSessionService } from "../shared/session/app-session.service";
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { HttpClientJsonpModule } from "@angular/common/http";
|
||||
import { HttpClientModule } from "@angular/common/http";
|
||||
import { ModalModule } from "ngx-bootstrap/modal";
|
||||
import { BsDropdownModule } from "ngx-bootstrap/dropdown";
|
||||
import { CollapseModule } from "ngx-bootstrap/collapse";
|
||||
import { TabsModule } from "ngx-bootstrap/tabs";
|
||||
import { NgxPaginationModule } from "ngx-pagination";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { ServiceProxyModule } from "../shared/service-proxies/service-proxy.module";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { HomeComponent } from "../app/home/home.component";
|
||||
import { AboutComponent } from "../app/about/about.component";
|
||||
|
||||
// layout
|
||||
import { HeaderComponent } from "./layout/header.component";
|
||||
import { HeaderLeftNavbarComponent } from "./layout/header-left-navbar.component";
|
||||
import { HeaderLanguageMenuComponent } from "./layout/header-language-menu.component";
|
||||
import { HeaderUserMenuComponent } from "./layout/header-user-menu.component";
|
||||
import { FooterComponent } from "./layout/footer.component";
|
||||
import { SidebarComponent } from "./layout/sidebar.component";
|
||||
import { SidebarLogoComponent } from "./layout/sidebar-logo.component";
|
||||
import { SidebarUserPanelComponent } from "./layout/sidebar-user-panel.component";
|
||||
import { SidebarMenuComponent } from "./layout/sidebar-menu.component";
|
||||
|
||||
describe("AppComponent", () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
AboutComponent,
|
||||
|
||||
// layout
|
||||
HeaderComponent,
|
||||
HeaderLeftNavbarComponent,
|
||||
HeaderLanguageMenuComponent,
|
||||
HeaderUserMenuComponent,
|
||||
FooterComponent,
|
||||
SidebarComponent,
|
||||
SidebarLogoComponent,
|
||||
SidebarUserPanelComponent,
|
||||
SidebarMenuComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
HttpClientJsonpModule,
|
||||
ModalModule.forChild(),
|
||||
BsDropdownModule.forRoot(),
|
||||
CollapseModule.forRoot(),
|
||||
TabsModule.forRoot(),
|
||||
RouterTestingModule,
|
||||
ServiceProxyModule,
|
||||
SharedModule.forRoot(),
|
||||
NgxPaginationModule,
|
||||
],
|
||||
providers: [
|
||||
LayoutStoreService,
|
||||
{
|
||||
provide: AppSessionService,
|
||||
useValue: {
|
||||
application: {
|
||||
version: "",
|
||||
releaseDate: {
|
||||
format: function () {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
},
|
||||
getShownLoginName: function(){
|
||||
return 'admin';
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
TestBed.compileComponents();
|
||||
});
|
||||
|
||||
it("should create the app", async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
});
|
48
angular/src/app/app.component.ts
Normal file
48
angular/src/app/app.component.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Component, Injector, OnInit, Renderer2 } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { SignalRAspNetCoreHelper } from '@shared/helpers/SignalRAspNetCoreHelper';
|
||||
import { LayoutStoreService } from '@shared/layout/layout-store.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent extends AppComponentBase implements OnInit {
|
||||
sidebarExpanded: boolean;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private renderer: Renderer2,
|
||||
private _layoutStore: LayoutStoreService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.renderer.addClass(document.body, 'sidebar-mini');
|
||||
|
||||
SignalRAspNetCoreHelper.initSignalR();
|
||||
|
||||
abp.event.on('abp.notifications.received', (userNotification) => {
|
||||
abp.notifications.showUiNotifyForUserNotification(userNotification);
|
||||
|
||||
// Desktop notification
|
||||
Push.create('AbpZeroTemplate', {
|
||||
body: userNotification.notification.data.message,
|
||||
icon: abp.appPath + 'assets/app-logo-small.png',
|
||||
timeout: 6000,
|
||||
onClick: function () {
|
||||
window.focus();
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._layoutStore.sidebarExpanded.subscribe((value) => {
|
||||
this.sidebarExpanded = value;
|
||||
});
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
this._layoutStore.setSidebarExpanded(!this.sidebarExpanded);
|
||||
}
|
||||
}
|
101
angular/src/app/app.module.ts
Normal file
101
angular/src/app/app.module.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientJsonpModule } from '@angular/common/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { ModalModule } from 'ngx-bootstrap/modal';
|
||||
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
|
||||
import { CollapseModule } from 'ngx-bootstrap/collapse';
|
||||
import { TabsModule } from 'ngx-bootstrap/tabs';
|
||||
import { NgxPaginationModule } from 'ngx-pagination';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ServiceProxyModule } from '@shared/service-proxies/service-proxy.module';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { HomeComponent } from '@app/home/home.component';
|
||||
import { AboutComponent } from '@app/about/about.component';
|
||||
// tenants
|
||||
import { TenantsComponent } from '@app/tenants/tenants.component';
|
||||
import { CreateTenantDialogComponent } from './tenants/create-tenant/create-tenant-dialog.component';
|
||||
import { EditTenantDialogComponent } from './tenants/edit-tenant/edit-tenant-dialog.component';
|
||||
// roles
|
||||
import { RolesComponent } from '@app/roles/roles.component';
|
||||
import { CreateRoleDialogComponent } from './roles/create-role/create-role-dialog.component';
|
||||
import { EditRoleDialogComponent } from './roles/edit-role/edit-role-dialog.component';
|
||||
// users
|
||||
import { UsersComponent } from '@app/users/users.component';
|
||||
import { CreateUserDialogComponent } from '@app/users/create-user/create-user-dialog.component';
|
||||
import { EditUserDialogComponent } from '@app/users/edit-user/edit-user-dialog.component';
|
||||
import { ChangePasswordComponent } from './users/change-password/change-password.component';
|
||||
import { ResetPasswordDialogComponent } from './users/reset-password/reset-password.component';
|
||||
// layout
|
||||
import { HeaderComponent } from './layout/header.component';
|
||||
import { HeaderLeftNavbarComponent } from './layout/header-left-navbar.component';
|
||||
import { HeaderLanguageMenuComponent } from './layout/header-language-menu.component';
|
||||
import { HeaderUserMenuComponent } from './layout/header-user-menu.component';
|
||||
import { FooterComponent } from './layout/footer.component';
|
||||
import { SidebarComponent } from './layout/sidebar.component';
|
||||
import { SidebarLogoComponent } from './layout/sidebar-logo.component';
|
||||
import { SidebarUserPanelComponent } from './layout/sidebar-user-panel.component';
|
||||
import { SidebarMenuComponent } from './layout/sidebar-menu.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
AboutComponent,
|
||||
// tenants
|
||||
TenantsComponent,
|
||||
CreateTenantDialogComponent,
|
||||
EditTenantDialogComponent,
|
||||
// roles
|
||||
RolesComponent,
|
||||
CreateRoleDialogComponent,
|
||||
EditRoleDialogComponent,
|
||||
// users
|
||||
UsersComponent,
|
||||
CreateUserDialogComponent,
|
||||
EditUserDialogComponent,
|
||||
ChangePasswordComponent,
|
||||
ResetPasswordDialogComponent,
|
||||
// layout
|
||||
HeaderComponent,
|
||||
HeaderLeftNavbarComponent,
|
||||
HeaderLanguageMenuComponent,
|
||||
HeaderUserMenuComponent,
|
||||
FooterComponent,
|
||||
SidebarComponent,
|
||||
SidebarLogoComponent,
|
||||
SidebarUserPanelComponent,
|
||||
SidebarMenuComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
HttpClientJsonpModule,
|
||||
ModalModule.forChild(),
|
||||
BsDropdownModule,
|
||||
CollapseModule,
|
||||
TabsModule,
|
||||
AppRoutingModule,
|
||||
ServiceProxyModule,
|
||||
SharedModule,
|
||||
NgxPaginationModule,
|
||||
],
|
||||
providers: [],
|
||||
entryComponents: [
|
||||
// tenants
|
||||
CreateTenantDialogComponent,
|
||||
EditTenantDialogComponent,
|
||||
// roles
|
||||
CreateRoleDialogComponent,
|
||||
EditRoleDialogComponent,
|
||||
// users
|
||||
CreateUserDialogComponent,
|
||||
EditUserDialogComponent,
|
||||
ResetPasswordDialogComponent,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
296
angular/src/app/home/home.component.html
Normal file
296
angular/src/app/home/home.component.html
Normal file
@ -0,0 +1,296 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>{{ "HomePage" | localize }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info alert-dismissible">
|
||||
<h6><i class="icon fa fa-info"></i> This is a sample Dashboard which doesn't show any server side data.
|
||||
However, you can develop your own dashboard inspired by this one and its source code.</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-6">
|
||||
<div class="small-box bg-success">
|
||||
<div class="inner">
|
||||
<h3>8.2k+</h3>
|
||||
<p>Stargazers</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-star"></i>
|
||||
</div>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/stargazers"
|
||||
class="small-box-footer"
|
||||
target="_blank">
|
||||
More info
|
||||
<i class="fas fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-6">
|
||||
<div class="small-box bg-info">
|
||||
<div class="inner">
|
||||
<h3>140+</h3>
|
||||
<p>Contributors</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/graphs/contributors"
|
||||
class="small-box-footer"
|
||||
target="_blank">
|
||||
More info
|
||||
<i class="fas fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-6">
|
||||
<div class="small-box bg-warning">
|
||||
<div class="inner">
|
||||
<h3>1.6k+</h3>
|
||||
<p>Used / Dependents</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-tools"></i>
|
||||
</div>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/network/dependents"
|
||||
class="small-box-footer"
|
||||
target="_blank">
|
||||
More info
|
||||
<i class="fas fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-6">
|
||||
<div class="small-box bg-primary">
|
||||
<div class="inner">
|
||||
<h3>3.1k+</h3>
|
||||
<p>Forks</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</div>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/network/members"
|
||||
class="small-box-footer"
|
||||
target="_blank">
|
||||
More info
|
||||
<i class="fas fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-primary elevation-1">
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Commits</span>
|
||||
<span class="info-box-number">
|
||||
6,350+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box mb-3">
|
||||
<span class="info-box-icon bg-warning elevation-1">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Issues</span>
|
||||
<span class="info-box-number">
|
||||
170+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix hidden-md-up"></div>
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box mb-3">
|
||||
<span class="info-box-icon bg-info elevation-1">
|
||||
<i class="fas fa-tag"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Releases</span>
|
||||
<span class="info-box-number">
|
||||
200+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box mb-3">
|
||||
<span class="info-box-icon bg-success elevation-1">
|
||||
<i class="fas fa-eye"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Watching by</span>
|
||||
<span class="info-box-number">
|
||||
810+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Open Issues</h3>
|
||||
</div>
|
||||
<div class="card-body table-responsive p-0">
|
||||
<table class="table table-hover text-nowrap">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th class="w-25">Title</th>
|
||||
<th class="w-25">Labels</th>
|
||||
<th>Date</th>
|
||||
<th>Opened by</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/5452"
|
||||
target="_blank">
|
||||
5452
|
||||
</a>
|
||||
</td>
|
||||
<td>Angular UI migration to AdminLTE 3</td>
|
||||
<td>
|
||||
<span class="badge badge-secondary mx-1">
|
||||
module-zero-core-template
|
||||
</span>
|
||||
<span class="badge badge-primary mx-1">
|
||||
feature
|
||||
</span>
|
||||
</td>
|
||||
<td>11 days ago</td>
|
||||
<td>iyilm4z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/5391"
|
||||
target="_blank">
|
||||
5391
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
AbpCacheBase should lock the same object for sync and
|
||||
async
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-danger mx-1">
|
||||
bug
|
||||
</span>
|
||||
<span class="badge badge-success mx-1">
|
||||
pull request candidate
|
||||
</span>
|
||||
</td>
|
||||
<td>26 days ago</td>
|
||||
<td>acjh</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/5390"
|
||||
target="_blank">
|
||||
5390
|
||||
</a>
|
||||
</td>
|
||||
<td>AbpCache sliding/absolute expire time</td>
|
||||
<td>
|
||||
<span class="badge badge-warning mx-1">
|
||||
breaking-change
|
||||
</span>
|
||||
<span class="badge badge-info mx-1">enhancement</span>
|
||||
</td>
|
||||
<td>27 days ago</td>
|
||||
<td>ryancyq</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Closed Pull Requests</h3>
|
||||
</div>
|
||||
<div class="card-body table-responsive p-0">
|
||||
<table class="table table-hover text-nowrap">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th class="w-25">Title</th>
|
||||
<th class="w-25">Milestone</th>
|
||||
<th>Date</th>
|
||||
<th>Made by</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/pull/5430"
|
||||
target="_blank">
|
||||
5430
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Added Dynamic-Parameter-System doc to documentation menu
|
||||
</td>
|
||||
<td>v5.6</td>
|
||||
<td>18 days ago</td>
|
||||
<td>maliming</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/pull/5362"
|
||||
target="_blank">
|
||||
5362
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Dynamic Parameter Module
|
||||
</td>
|
||||
<td>v5.4</td>
|
||||
<td>25 days ago</td>
|
||||
<td>demirmusa</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/aspnetboilerplate/aspnetboilerplate/pull/4924"
|
||||
target="_blank">
|
||||
4924
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
ASP.NET Core 3.0 Upgrade
|
||||
</td>
|
||||
<td>v5.0</td>
|
||||
<td>Oct 15</td>
|
||||
<td>ismcagdas</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
14
angular/src/app/home/home.component.ts
Normal file
14
angular/src/app/home/home.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component, Injector, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
|
||||
@Component({
|
||||
templateUrl: './home.component.html',
|
||||
animations: [appModuleAnimation()],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HomeComponent extends AppComponentBase {
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
}
|
||||
}
|
9
angular/src/app/layout/footer.component.html
Normal file
9
angular/src/app/layout/footer.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<footer class="main-footer">
|
||||
<strong>
|
||||
Copyright © {{ currentYear }}
|
||||
<a href="javascript:;">MeetingSchedule</a>.
|
||||
</strong>
|
||||
<div class="float-right d-none d-sm-inline-block">
|
||||
<b>{{ "Version" | localize }} </b> {{ versionText }}
|
||||
</div>
|
||||
</footer>
|
23
angular/src/app/layout/footer.component.ts
Normal file
23
angular/src/app/layout/footer.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Component, Injector, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FooterComponent extends AppComponentBase {
|
||||
currentYear: number;
|
||||
versionText: string;
|
||||
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
|
||||
this.currentYear = new Date().getFullYear();
|
||||
this.versionText =
|
||||
this.appSession.application.version +
|
||||
' [' +
|
||||
this.appSession.application.releaseDate.format('YYYYDDMM') +
|
||||
']';
|
||||
}
|
||||
}
|
21
angular/src/app/layout/header-language-menu.component.html
Normal file
21
angular/src/app/layout/header-language-menu.component.html
Normal file
@ -0,0 +1,21 @@
|
||||
<li class="nav-item dropdown" dropdown>
|
||||
<a href="javascript:;" class="nav-link" dropdownToggle>
|
||||
<i class="d-inline-block {{ currentLanguage.icon }}"></i>
|
||||
<span class="d-none d-md-inline-block ml-1">
|
||||
{{ currentLanguage.displayName }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right p-0" *dropdownMenu>
|
||||
<ng-container *ngFor="let language of languages">
|
||||
<a
|
||||
*ngIf="language.name != currentLanguage.name"
|
||||
class="dropdown-item"
|
||||
href="javascript:;"
|
||||
(click)="changeLanguage(language.name)"
|
||||
>
|
||||
<i class="d-inline-block {{ language.icon }} mr-1"></i>
|
||||
{{ language.displayName }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</li>
|
51
angular/src/app/layout/header-language-menu.component.ts
Normal file
51
angular/src/app/layout/header-language-menu.component.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
OnInit,
|
||||
Injector
|
||||
} from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
UserServiceProxy,
|
||||
ChangeUserLanguageDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { filter as _filter } from 'lodash-es';
|
||||
|
||||
@Component({
|
||||
selector: 'header-language-menu',
|
||||
templateUrl: './header-language-menu.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderLanguageMenuComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
languages: abp.localization.ILanguageInfo[];
|
||||
currentLanguage: abp.localization.ILanguageInfo;
|
||||
|
||||
constructor(injector: Injector, private _userService: UserServiceProxy) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.languages = _filter(
|
||||
this.localization.languages,
|
||||
(l) => !l.isDisabled
|
||||
);
|
||||
this.currentLanguage = this.localization.currentLanguage;
|
||||
}
|
||||
|
||||
changeLanguage(languageName: string): void {
|
||||
const input = new ChangeUserLanguageDto();
|
||||
input.languageName = languageName;
|
||||
|
||||
this._userService.changeLanguage(input).subscribe(() => {
|
||||
abp.utils.setCookieValue(
|
||||
'Abp.Localization.CultureName',
|
||||
languageName,
|
||||
new Date(new Date().getTime() + 5 * 365 * 86400000), // 5 year
|
||||
abp.appPath
|
||||
);
|
||||
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
17
angular/src/app/layout/header-left-navbar.component.html
Normal file
17
angular/src/app/layout/header-left-navbar.component.html
Normal file
@ -0,0 +1,17 @@
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:;" (click)="toggleSidebar()">
|
||||
<i class="fas fa-bars"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item d-none d-sm-inline-block">
|
||||
<a class="nav-link" [routerLink]="['home']">
|
||||
{{ "HomePage" | localize }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item d-none d-sm-inline-block">
|
||||
<a class="nav-link" [routerLink]="['about']">
|
||||
{{ "About" | localize }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
23
angular/src/app/layout/header-left-navbar.component.ts
Normal file
23
angular/src/app/layout/header-left-navbar.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { LayoutStoreService } from '@shared/layout/layout-store.service';
|
||||
|
||||
@Component({
|
||||
selector: 'header-left-navbar',
|
||||
templateUrl: './header-left-navbar.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderLeftNavbarComponent implements OnInit {
|
||||
sidebarExpanded: boolean;
|
||||
|
||||
constructor(private _layoutStore: LayoutStoreService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._layoutStore.sidebarExpanded.subscribe((value) => {
|
||||
this.sidebarExpanded = value;
|
||||
});
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
this._layoutStore.setSidebarExpanded(!this.sidebarExpanded);
|
||||
}
|
||||
}
|
17
angular/src/app/layout/header-user-menu.component.html
Normal file
17
angular/src/app/layout/header-user-menu.component.html
Normal file
@ -0,0 +1,17 @@
|
||||
<li class="nav-item dropdown nav-user-menu" dropdown>
|
||||
<a href="javascript:;" class="nav-link" dropdownToggle>
|
||||
<img
|
||||
class="user-image img-circle elevation-2"
|
||||
src="assets/img/user.png"
|
||||
alt="User Image"
|
||||
/>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" *dropdownMenu>
|
||||
<a class="dropdown-item" [routerLink]="['update-password']">
|
||||
<i class="fas fa-user-edit"></i> {{ "UpdatePassword" | localize }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="javascript:;" (click)="logout()">
|
||||
<i class="fas fa-sign-out-alt"></i> {{ "Logout" | localize }}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
15
angular/src/app/layout/header-user-menu.component.ts
Normal file
15
angular/src/app/layout/header-user-menu.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { AppAuthService } from '@shared/auth/app-auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'header-user-menu',
|
||||
templateUrl: './header-user-menu.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderUserMenuComponent {
|
||||
constructor(private _authService: AppAuthService) {}
|
||||
|
||||
logout(): void {
|
||||
this._authService.logout();
|
||||
}
|
||||
}
|
7
angular/src/app/layout/header.component.html
Normal file
7
angular/src/app/layout/header.component.html
Normal file
@ -0,0 +1,7 @@
|
||||
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||
<header-left-navbar></header-left-navbar>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<header-language-menu></header-language-menu>
|
||||
<header-user-menu></header-user-menu>
|
||||
</ul>
|
||||
</nav>
|
8
angular/src/app/layout/header.component.ts
Normal file
8
angular/src/app/layout/header.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderComponent {}
|
9
angular/src/app/layout/sidebar-logo.component.html
Normal file
9
angular/src/app/layout/sidebar-logo.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<a class="brand-link" [routerLink]="['home']">
|
||||
<img
|
||||
src="assets/img/logo.png"
|
||||
alt="Logo"
|
||||
class="brand-image img-circle elevation-3"
|
||||
style="opacity: 0.8;"
|
||||
/>
|
||||
<span class="brand-text font-weight-light">MeetingSchedule</span>
|
||||
</a>
|
8
angular/src/app/layout/sidebar-logo.component.ts
Normal file
8
angular/src/app/layout/sidebar-logo.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'sidebar-logo',
|
||||
templateUrl: './sidebar-logo.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SidebarLogoComponent {}
|
71
angular/src/app/layout/sidebar-menu.component.html
Normal file
71
angular/src/app/layout/sidebar-menu.component.html
Normal file
@ -0,0 +1,71 @@
|
||||
<nav class="mt-2">
|
||||
<ul
|
||||
class="nav nav-pills nav-sidebar flex-column nav-flat"
|
||||
data-widget="treeview"
|
||||
role="menu"
|
||||
data-accordion="false"
|
||||
>
|
||||
<ng-container *ngFor="let item of menuItems">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="sidebarInner; context: { item: item }"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<ng-template #sidebarInner let-item="item">
|
||||
<li
|
||||
*ngIf="isMenuItemVisible(item)"
|
||||
class="nav-item"
|
||||
[class.menu-open]="!item.isCollapsed"
|
||||
[class.has-treeview]="item.children"
|
||||
>
|
||||
<a
|
||||
*ngIf="item.route && item.route.indexOf('http') != 0"
|
||||
class="nav-link"
|
||||
[routerLink]="item.route"
|
||||
[class.active]="item.isActive"
|
||||
>
|
||||
<i class="nav-icon {{ item.icon }}"></i>
|
||||
<p>
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="item.route && item.route.indexOf('http') == 0 && !item.children"
|
||||
class="nav-link"
|
||||
target="_blank"
|
||||
[href]="item.route"
|
||||
>
|
||||
<i class="nav-icon {{ item.icon }}"></i>
|
||||
<p>
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="!item.route && item.children"
|
||||
class="nav-link"
|
||||
href="javascript:;"
|
||||
[class.active]="item.isActive"
|
||||
(click)="item.isCollapsed = !item.isCollapsed"
|
||||
>
|
||||
<i class="nav-icon {{ item.icon }}"></i>
|
||||
<p>
|
||||
{{ item.label }}
|
||||
<i class="right fas fa-angle-left"></i>
|
||||
</p>
|
||||
</a>
|
||||
<ul
|
||||
*ngIf="item.children"
|
||||
class="nav nav-treeview"
|
||||
[collapse]="item.isCollapsed"
|
||||
[isAnimated]="true"
|
||||
>
|
||||
<ng-container *ngFor="let item of item.children">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="sidebarInner; context: { item: item }"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
186
angular/src/app/layout/sidebar-menu.component.ts
Normal file
186
angular/src/app/layout/sidebar-menu.component.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import {Component, Injector, OnInit} from '@angular/core';
|
||||
import {AppComponentBase} from '@shared/app-component-base';
|
||||
import {
|
||||
Router,
|
||||
RouterEvent,
|
||||
NavigationEnd,
|
||||
PRIMARY_OUTLET
|
||||
} from '@angular/router';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {MenuItem} from '@shared/layout/menu-item';
|
||||
|
||||
@Component({
|
||||
selector: 'sidebar-menu',
|
||||
templateUrl: './sidebar-menu.component.html'
|
||||
})
|
||||
export class SidebarMenuComponent extends AppComponentBase implements OnInit {
|
||||
menuItems: MenuItem[];
|
||||
menuItemsMap: { [key: number]: MenuItem } = {};
|
||||
activatedMenuItems: MenuItem[] = [];
|
||||
routerEvents: BehaviorSubject<RouterEvent> = new BehaviorSubject(undefined);
|
||||
homeRoute = '/app/about';
|
||||
|
||||
constructor(injector: Injector, private router: Router) {
|
||||
super(injector);
|
||||
this.router.events.subscribe(this.routerEvents);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.menuItems = this.getMenuItems();
|
||||
this.patchMenuItems(this.menuItems);
|
||||
this.routerEvents
|
||||
.pipe(filter((event) => event instanceof NavigationEnd))
|
||||
.subscribe((event) => {
|
||||
const currentUrl = event.url !== '/' ? event.url : this.homeRoute;
|
||||
const primaryUrlSegmentGroup = this.router.parseUrl(currentUrl).root
|
||||
.children[PRIMARY_OUTLET];
|
||||
if (primaryUrlSegmentGroup) {
|
||||
this.activateMenuItems('/' + primaryUrlSegmentGroup.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMenuItems(): MenuItem[] {
|
||||
return [
|
||||
new MenuItem(this.l('About'), '/app/about', 'fas fa-info-circle'),
|
||||
new MenuItem(this.l('HomePage'), '/app/home', 'fas fa-home'),
|
||||
new MenuItem(
|
||||
this.l('Roles'),
|
||||
'/app/roles',
|
||||
'fas fa-theater-masks',
|
||||
'Pages.Roles'
|
||||
),
|
||||
new MenuItem(
|
||||
this.l('Tenants'),
|
||||
'/app/tenants',
|
||||
'fas fa-building',
|
||||
'Pages.Tenants'
|
||||
),
|
||||
new MenuItem(
|
||||
this.l('Users'),
|
||||
'/app/users',
|
||||
'fas fa-users',
|
||||
'Pages.Users'
|
||||
),
|
||||
new MenuItem(this.l('MultiLevelMenu'), '', 'fas fa-circle', '', [
|
||||
new MenuItem('ASP.NET Boilerplate', '', 'fas fa-dot-circle', '', [
|
||||
new MenuItem(
|
||||
'Home',
|
||||
'https://aspnetboilerplate.com?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Templates',
|
||||
'https://aspnetboilerplate.com/Templates?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Samples',
|
||||
'https://aspnetboilerplate.com/Samples?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Documents',
|
||||
'https://aspnetboilerplate.com/Pages/Documents?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
]),
|
||||
new MenuItem('ASP.NET Zero', '', 'fas fa-dot-circle', '', [
|
||||
new MenuItem(
|
||||
'Home',
|
||||
'https://aspnetzero.com?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Features',
|
||||
'https://aspnetzero.com/Features?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Pricing',
|
||||
'https://aspnetzero.com/Pricing?ref=abptmpl#pricing',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Faq',
|
||||
'https://aspnetzero.com/Faq?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
),
|
||||
new MenuItem(
|
||||
'Documents',
|
||||
'https://aspnetzero.com/Documents?ref=abptmpl',
|
||||
'far fa-circle'
|
||||
)
|
||||
])
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
patchMenuItems(items: MenuItem[], parentId?: number): void {
|
||||
items.forEach((item: MenuItem, index: number) => {
|
||||
item.id = parentId ? Number(parentId + '' + (index + 1)) : index + 1;
|
||||
if (parentId) {
|
||||
item.parentId = parentId;
|
||||
}
|
||||
if (parentId || item.children) {
|
||||
this.menuItemsMap[item.id] = item;
|
||||
}
|
||||
if (item.children) {
|
||||
this.patchMenuItems(item.children, item.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
activateMenuItems(url: string): void {
|
||||
this.deactivateMenuItems(this.menuItems);
|
||||
this.activatedMenuItems = [];
|
||||
const foundedItems = this.findMenuItemsByUrl(url, this.menuItems);
|
||||
foundedItems.forEach((item) => {
|
||||
this.activateMenuItem(item);
|
||||
});
|
||||
}
|
||||
|
||||
deactivateMenuItems(items: MenuItem[]): void {
|
||||
items.forEach((item: MenuItem) => {
|
||||
item.isActive = false;
|
||||
item.isCollapsed = true;
|
||||
if (item.children) {
|
||||
this.deactivateMenuItems(item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
findMenuItemsByUrl(
|
||||
url: string,
|
||||
items: MenuItem[],
|
||||
foundedItems: MenuItem[] = []
|
||||
): MenuItem[] {
|
||||
items.forEach((item: MenuItem) => {
|
||||
if (item.route === url) {
|
||||
foundedItems.push(item);
|
||||
} else if (item.children) {
|
||||
this.findMenuItemsByUrl(url, item.children, foundedItems);
|
||||
}
|
||||
});
|
||||
return foundedItems;
|
||||
}
|
||||
|
||||
activateMenuItem(item: MenuItem): void {
|
||||
item.isActive = true;
|
||||
if (item.children) {
|
||||
item.isCollapsed = false;
|
||||
}
|
||||
this.activatedMenuItems.push(item);
|
||||
if (item.parentId) {
|
||||
this.activateMenuItem(this.menuItemsMap[item.parentId]);
|
||||
}
|
||||
}
|
||||
|
||||
isMenuItemVisible(item: MenuItem): boolean {
|
||||
if (!item.permissionName) {
|
||||
return true;
|
||||
}
|
||||
return this.permission.isGranted(item.permissionName);
|
||||
}
|
||||
}
|
12
angular/src/app/layout/sidebar-user-panel.component.html
Normal file
12
angular/src/app/layout/sidebar-user-panel.component.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
||||
<div class="image">
|
||||
<img
|
||||
src="assets/img/user.png"
|
||||
class="img-circle elevation-2"
|
||||
alt="User Image"
|
||||
/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a class="d-block" href="javascript:;">{{ shownLoginName }}</a>
|
||||
</div>
|
||||
</div>
|
25
angular/src/app/layout/sidebar-user-panel.component.ts
Normal file
25
angular/src/app/layout/sidebar-user-panel.component.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Injector,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
|
||||
@Component({
|
||||
selector: 'sidebar-user-panel',
|
||||
templateUrl: './sidebar-user-panel.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SidebarUserPanelComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
shownLoginName = '';
|
||||
|
||||
constructor(injector: Injector) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.shownLoginName = this.appSession.getShownLoginName();
|
||||
}
|
||||
}
|
7
angular/src/app/layout/sidebar.component.html
Normal file
7
angular/src/app/layout/sidebar.component.html
Normal file
@ -0,0 +1,7 @@
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
||||
<sidebar-logo></sidebar-logo>
|
||||
<div class="sidebar">
|
||||
<sidebar-user-panel></sidebar-user-panel>
|
||||
<sidebar-menu></sidebar-menu>
|
||||
</div>
|
||||
</aside>
|
47
angular/src/app/layout/sidebar.component.ts
Normal file
47
angular/src/app/layout/sidebar.component.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Renderer2,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { LayoutStoreService } from '@shared/layout/layout-store.service';
|
||||
|
||||
@Component({
|
||||
// tslint:disable-next-line:component-selector
|
||||
selector: 'sidebar',
|
||||
templateUrl: './sidebar.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SidebarComponent implements OnInit {
|
||||
sidebarExpanded: boolean;
|
||||
|
||||
constructor(
|
||||
private renderer: Renderer2,
|
||||
private _layoutStore: LayoutStoreService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._layoutStore.sidebarExpanded.subscribe((value) => {
|
||||
this.sidebarExpanded = value;
|
||||
this.toggleSidebar();
|
||||
});
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
if (this.sidebarExpanded) {
|
||||
this.hideSidebar();
|
||||
} else {
|
||||
this.showSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
showSidebar(): void {
|
||||
this.renderer.removeClass(document.body, 'sidebar-collapse');
|
||||
this.renderer.addClass(document.body, 'sidebar-open');
|
||||
}
|
||||
|
||||
hideSidebar(): void {
|
||||
this.renderer.removeClass(document.body, 'sidebar-open');
|
||||
this.renderer.addClass(document.body, 'sidebar-collapse');
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#createRoleForm="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'CreateNewRole' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<tabset>
|
||||
<tab [heading]="'Details' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="role.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="displayName">
|
||||
{{ "DisplayName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="displayName"
|
||||
id="displayName"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="role.displayName"
|
||||
#displayNameModel="ngModel"
|
||||
#displayNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="displayNameModel"
|
||||
[controlEl]="displayNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label" for="description">
|
||||
{{ "RoleDescription" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<textarea
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="description"
|
||||
id="description"
|
||||
[(ngModel)]="role.description"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
<tab [heading]="'Permissions' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row mb-0">
|
||||
<ng-container *ngFor="let permission of permissions; let i = index">
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
[id]="'permission_' + i"
|
||||
[checked]="isPermissionChecked(permission.name)"
|
||||
(change)="onPermissionChange(permission, $event)"
|
||||
/>
|
||||
<label class="custom-control-label" [for]="'permission_' + i">
|
||||
{{ permission.displayName }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!createRoleForm.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,97 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
RoleServiceProxy,
|
||||
RoleDto,
|
||||
PermissionDto,
|
||||
CreateRoleDto,
|
||||
PermissionDtoListResultDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { forEach as _forEach, map as _map } from 'lodash-es';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'create-role-dialog.component.html'
|
||||
})
|
||||
export class CreateRoleDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
role = new RoleDto();
|
||||
permissions: PermissionDto[] = [];
|
||||
checkedPermissionsMap: { [key: string]: boolean } = {};
|
||||
defaultPermissionCheckedStatus = true;
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _roleService: RoleServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._roleService
|
||||
.getAllPermissions()
|
||||
.subscribe((result: PermissionDtoListResultDto) => {
|
||||
this.permissions = result.items;
|
||||
this.setInitialPermissionsStatus();
|
||||
});
|
||||
}
|
||||
|
||||
setInitialPermissionsStatus(): void {
|
||||
_map(this.permissions, (item) => {
|
||||
this.checkedPermissionsMap[item.name] = this.isPermissionChecked(
|
||||
item.name
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
isPermissionChecked(permissionName: string): boolean {
|
||||
// just return default permission checked status
|
||||
// it's better to use a setting
|
||||
return this.defaultPermissionCheckedStatus;
|
||||
}
|
||||
|
||||
onPermissionChange(permission: PermissionDto, $event) {
|
||||
this.checkedPermissionsMap[permission.name] = $event.target.checked;
|
||||
}
|
||||
|
||||
getCheckedPermissions(): string[] {
|
||||
const permissions: string[] = [];
|
||||
_forEach(this.checkedPermissionsMap, function (value, key) {
|
||||
if (value) {
|
||||
permissions.push(key);
|
||||
}
|
||||
});
|
||||
return permissions;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
const role = new CreateRoleDto();
|
||||
role.init(this.role);
|
||||
role.grantedPermissions = this.getCheckedPermissions();
|
||||
|
||||
this._roleService
|
||||
.create(role)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
104
angular/src/app/roles/edit-role/edit-role-dialog.component.html
Normal file
104
angular/src/app/roles/edit-role/edit-role-dialog.component.html
Normal file
@ -0,0 +1,104 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#editRoleForm="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'EditRole' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<tabset>
|
||||
<tab [heading]="'Details' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="role.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="displayName">
|
||||
{{ "DisplayName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="displayName"
|
||||
id="displayName"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="role.displayName"
|
||||
#displayNameModel="ngModel"
|
||||
#displayNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="displayNameModel"
|
||||
[controlEl]="displayNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label" for="description">
|
||||
{{ "RoleDescription" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<textarea
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="description"
|
||||
id="description"
|
||||
[(ngModel)]="role.description"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
<tab [heading]="'Permissions' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row mb-0">
|
||||
<ng-container *ngFor="let permission of permissions; let i = index">
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
[id]="'permission_' + i"
|
||||
[checked]="isPermissionChecked(permission.name)"
|
||||
[disabled]="role.isStatic"
|
||||
(change)="onPermissionChange(permission, $event)"
|
||||
/>
|
||||
<label class="custom-control-label" [for]="'permission_' + i">
|
||||
{{ permission.displayName }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!editRoleForm.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,97 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { forEach as _forEach, includes as _includes, map as _map } from 'lodash-es';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
RoleServiceProxy,
|
||||
GetRoleForEditOutput,
|
||||
RoleDto,
|
||||
PermissionDto,
|
||||
RoleEditDto,
|
||||
FlatPermissionDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'edit-role-dialog.component.html'
|
||||
})
|
||||
export class EditRoleDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
id: number;
|
||||
role = new RoleEditDto();
|
||||
permissions: FlatPermissionDto[];
|
||||
grantedPermissionNames: string[];
|
||||
checkedPermissionsMap: { [key: string]: boolean } = {};
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _roleService: RoleServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._roleService
|
||||
.getRoleForEdit(this.id)
|
||||
.subscribe((result: GetRoleForEditOutput) => {
|
||||
this.role = result.role;
|
||||
this.permissions = result.permissions;
|
||||
this.grantedPermissionNames = result.grantedPermissionNames;
|
||||
this.setInitialPermissionsStatus();
|
||||
});
|
||||
}
|
||||
|
||||
setInitialPermissionsStatus(): void {
|
||||
_map(this.permissions, (item) => {
|
||||
this.checkedPermissionsMap[item.name] = this.isPermissionChecked(
|
||||
item.name
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
isPermissionChecked(permissionName: string): boolean {
|
||||
return _includes(this.grantedPermissionNames, permissionName);
|
||||
}
|
||||
|
||||
onPermissionChange(permission: PermissionDto, $event) {
|
||||
this.checkedPermissionsMap[permission.name] = $event.target.checked;
|
||||
}
|
||||
|
||||
getCheckedPermissions(): string[] {
|
||||
const permissions: string[] = [];
|
||||
_forEach(this.checkedPermissionsMap, function (value, key) {
|
||||
if (value) {
|
||||
permissions.push(key);
|
||||
}
|
||||
});
|
||||
return permissions;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
const role = new RoleDto();
|
||||
role.init(this.role);
|
||||
role.grantedPermissions = this.getCheckedPermissions();
|
||||
|
||||
this._roleService.update(role).subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
112
angular/src/app/roles/roles.component.html
Normal file
112
angular/src/app/roles/roles.component.html
Normal file
@ -0,0 +1,112 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h1>{{ "Roles" | localize }}</h1>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<a href="javascript:;"
|
||||
class="btn bg-blue"
|
||||
(click)="createRole()">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
{{ "Create" | localize }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md-6"> </div>
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button type="button"
|
||||
class="btn bg-blue"
|
||||
(click)="getDataPage(1)">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="keyword"
|
||||
[placeholder]="'SearchWithThreeDot' | localize"
|
||||
[(ngModel)]="keyword"
|
||||
(keyup.enter)="getDataPage(1)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered"
|
||||
[busy]="isTableLoading">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{{ "RoleName" | localize }}</th>
|
||||
<th>{{ "DisplayName" | localize }}</th>
|
||||
<th style="width: 200px;">{{ "Actions" | localize }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="
|
||||
let role of roles
|
||||
| paginate
|
||||
: {
|
||||
id: 'server',
|
||||
itemsPerPage: pageSize,
|
||||
currentPage: pageNumber,
|
||||
totalItems: totalItems
|
||||
}
|
||||
">
|
||||
<td>{{ role.name }}</td>
|
||||
<td>{{ role.displayName }}</td>
|
||||
<td>
|
||||
<button type="button"
|
||||
class="btn btn-sm bg-secondary"
|
||||
(click)="editRole(role)">
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
{{ "Edit" | localize }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm bg-danger mx-2"
|
||||
(click)="delete(role)">
|
||||
<i class="fas fa-trash"></i>
|
||||
{{ "Delete" | localize }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer table-card-footer bg-light border-top">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-12 text-sm-left text-center">
|
||||
<button class="btn btn-secondary"
|
||||
(click)="refresh()">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12 text-center">
|
||||
<p class="mb-0 my-2">
|
||||
{{ "TotalRecordsCount" | localize: totalItems }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12">
|
||||
<div class="float-sm-right m-auto">
|
||||
<abp-pagination-controls id="server"
|
||||
(pageChange)="getDataPage($event)">
|
||||
</abp-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
110
angular/src/app/roles/roles.component.ts
Normal file
110
angular/src/app/roles/roles.component.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import {
|
||||
PagedListingComponentBase,
|
||||
PagedRequestDto
|
||||
} from '@shared/paged-listing-component-base';
|
||||
import {
|
||||
RoleServiceProxy,
|
||||
RoleDto,
|
||||
RoleDtoPagedResultDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { CreateRoleDialogComponent } from './create-role/create-role-dialog.component';
|
||||
import { EditRoleDialogComponent } from './edit-role/edit-role-dialog.component';
|
||||
|
||||
class PagedRolesRequestDto extends PagedRequestDto {
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './roles.component.html',
|
||||
animations: [appModuleAnimation()]
|
||||
})
|
||||
export class RolesComponent extends PagedListingComponentBase<RoleDto> {
|
||||
roles: RoleDto[] = [];
|
||||
keyword = '';
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _rolesService: RoleServiceProxy,
|
||||
private _modalService: BsModalService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
list(
|
||||
request: PagedRolesRequestDto,
|
||||
pageNumber: number,
|
||||
finishedCallback: Function
|
||||
): void {
|
||||
request.keyword = this.keyword;
|
||||
|
||||
this._rolesService
|
||||
.getAll(request.keyword, request.skipCount, request.maxResultCount)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
finishedCallback();
|
||||
})
|
||||
)
|
||||
.subscribe((result: RoleDtoPagedResultDto) => {
|
||||
this.roles = result.items;
|
||||
this.showPaging(result, pageNumber);
|
||||
});
|
||||
}
|
||||
|
||||
delete(role: RoleDto): void {
|
||||
abp.message.confirm(
|
||||
this.l('RoleDeleteWarningMessage', role.displayName),
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
this._rolesService
|
||||
.delete(role.id)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
abp.notify.success(this.l('SuccessfullyDeleted'));
|
||||
this.refresh();
|
||||
})
|
||||
)
|
||||
.subscribe(() => {});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createRole(): void {
|
||||
this.showCreateOrEditRoleDialog();
|
||||
}
|
||||
|
||||
editRole(role: RoleDto): void {
|
||||
this.showCreateOrEditRoleDialog(role.id);
|
||||
}
|
||||
|
||||
showCreateOrEditRoleDialog(id?: number): void {
|
||||
let createOrEditRoleDialog: BsModalRef;
|
||||
if (!id) {
|
||||
createOrEditRoleDialog = this._modalService.show(
|
||||
CreateRoleDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
createOrEditRoleDialog = this._modalService.show(
|
||||
EditRoleDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
initialState: {
|
||||
id: id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createOrEditRoleDialog.content.onSave.subscribe(() => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#createTenantForm="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'CreateNewTenant' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="tenancyName">
|
||||
{{ "TenancyName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="tenancyName"
|
||||
id="tenancyName"
|
||||
minlength="2"
|
||||
maxlength="64"
|
||||
required
|
||||
[(ngModel)]="tenant.tenancyName"
|
||||
#tenancyNameModel="ngModel"
|
||||
#tenancyNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="tenancyNameModel"
|
||||
[controlEl]="tenancyNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
maxlength="128"
|
||||
required
|
||||
[(ngModel)]="tenant.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="connectionString">
|
||||
{{ "DatabaseConnectionString" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="connectionString"
|
||||
id="connectionString"
|
||||
maxlength="1024"
|
||||
[(ngModel)]="tenant.connectionString"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="adminEmailAddress">
|
||||
{{ "AdminEmailAddress" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
name="adminEmailAddress"
|
||||
id="adminEmailAddress"
|
||||
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$"
|
||||
maxlength="256"
|
||||
required
|
||||
[(ngModel)]="tenant.adminEmailAddress"
|
||||
#adminEmailAddressModel="ngModel"
|
||||
#adminEmailAddressEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="adminEmailAddressModel"
|
||||
[controlEl]="adminEmailAddressEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
name="isActive"
|
||||
id="isActive"
|
||||
[(ngModel)]="tenant.isActive"
|
||||
/>
|
||||
<label class="custom-control-label mt-2" for="isActive"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-info mb-0">
|
||||
{{ "DefaultPasswordIs" | localize: "123qwe" }}
|
||||
</p>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!createTenantForm.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,51 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
CreateTenantDto,
|
||||
TenantServiceProxy
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'create-tenant-dialog.component.html'
|
||||
})
|
||||
export class CreateTenantDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
tenant: CreateTenantDto = new CreateTenantDto();
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
public _tenantService: TenantServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.tenant.isActive = true;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
this._tenantService.create(this.tenant).subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#editTenantForm="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'EditTenant' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="tenancyName">
|
||||
{{ "TenancyName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="tenancyName"
|
||||
id="tenancyName"
|
||||
minlength="2"
|
||||
maxlength="64"
|
||||
required
|
||||
[(ngModel)]="tenant.tenancyName"
|
||||
#tenancyNameModel="ngModel"
|
||||
#tenancyNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="tenancyNameModel"
|
||||
[controlEl]="tenancyNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
maxlength="128"
|
||||
required
|
||||
[(ngModel)]="tenant.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
name="isActive"
|
||||
id="isActive"
|
||||
[(ngModel)]="tenant.isActive"
|
||||
/>
|
||||
<label class="custom-control-label mt-2" for="isActive"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!editTenantForm.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,54 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
TenantServiceProxy,
|
||||
TenantDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'edit-tenant-dialog.component.html'
|
||||
})
|
||||
export class EditTenantDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
tenant: TenantDto = new TenantDto();
|
||||
id: number;
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
public _tenantService: TenantServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._tenantService.get(this.id).subscribe((result: TenantDto) => {
|
||||
this.tenant = result;
|
||||
});
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
this._tenantService.update(this.tenant).subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
224
angular/src/app/tenants/tenants.component.html
Normal file
224
angular/src/app/tenants/tenants.component.html
Normal file
@ -0,0 +1,224 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h1>{{ "Tenants" | localize }}</h1>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<a href="javascript:;" class="btn bg-blue" (click)="createTenant()">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
{{ "Create" | localize }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md-6"> </div>
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button
|
||||
type="button"
|
||||
class="btn bg-blue"
|
||||
(click)="getDataPage(1)"
|
||||
>
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="keyword"
|
||||
[placeholder]="'SearchWithThreeDot' | localize"
|
||||
[(ngModel)]="keyword"
|
||||
(keyup.enter)="getDataPage(1)"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
(click)="advancedFiltersVisible = !advancedFiltersVisible"
|
||||
>
|
||||
<i
|
||||
class="fas"
|
||||
[class.fa-angle-up]="advancedFiltersVisible"
|
||||
[class.fa-angle-down]="!advancedFiltersVisible"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="advancedFiltersVisible" class="card mb-0 mt-1">
|
||||
<div class="card-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9 pt-2">
|
||||
<div class="custom-control custom-radio d-inline">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActiveAll"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="undefined"
|
||||
checked
|
||||
/>
|
||||
<label class="custom-control-label" for="isActiveAll">
|
||||
{{ "All" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio d-inline mx-3">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActiveActive"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="true"
|
||||
/>
|
||||
<label
|
||||
class="custom-control-label"
|
||||
for="isActiveActive"
|
||||
>
|
||||
{{ "Yes" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio d-inline">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActivePassive"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="false"
|
||||
/>
|
||||
<label
|
||||
class="custom-control-label"
|
||||
for="isActivePassive"
|
||||
>
|
||||
{{ "No" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn bg-blue"
|
||||
(click)="getDataPage(1)"
|
||||
>
|
||||
{{ "Search" | localize }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default float-right"
|
||||
(click)="clearFilters()"
|
||||
>
|
||||
{{ "Clear" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered" [busy]="isTableLoading">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{{ "TenancyName" | localize }}</th>
|
||||
<th>{{ "Name" | localize }}</th>
|
||||
<th>{{ "IsActive" | localize }}</th>
|
||||
<th style="width: 200px;">{{ "Actions" | localize }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
*ngFor="
|
||||
let tenant of tenants
|
||||
| paginate
|
||||
: {
|
||||
id: 'server',
|
||||
itemsPerPage: pageSize,
|
||||
currentPage: pageNumber,
|
||||
totalItems: totalItems
|
||||
}
|
||||
"
|
||||
>
|
||||
<td>{{ tenant.tenancyName }}</td>
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
disabled
|
||||
[checked]="tenant.isActive"
|
||||
/>
|
||||
<label class="custom-control-label"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-secondary"
|
||||
(click)="editTenant(tenant)"
|
||||
>
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
{{ "Edit" | localize }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-danger mx-2"
|
||||
(click)="delete(tenant)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
{{ "Delete" | localize }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer table-card-footer bg-light border-top">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-12 text-sm-left text-center">
|
||||
<button class="btn btn-secondary" (click)="refresh()">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12 text-center">
|
||||
<p class="mb-0 my-2">
|
||||
{{ "TotalRecordsCount" | localize: totalItems }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12">
|
||||
<div class="float-sm-right m-auto">
|
||||
<abp-pagination-controls
|
||||
id="server"
|
||||
(pageChange)="getDataPage($event)"
|
||||
>
|
||||
</abp-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
125
angular/src/app/tenants/tenants.component.ts
Normal file
125
angular/src/app/tenants/tenants.component.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import {
|
||||
PagedListingComponentBase,
|
||||
PagedRequestDto,
|
||||
} from '@shared/paged-listing-component-base';
|
||||
import {
|
||||
TenantServiceProxy,
|
||||
TenantDto,
|
||||
TenantDtoPagedResultDto,
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { CreateTenantDialogComponent } from './create-tenant/create-tenant-dialog.component';
|
||||
import { EditTenantDialogComponent } from './edit-tenant/edit-tenant-dialog.component';
|
||||
|
||||
class PagedTenantsRequestDto extends PagedRequestDto {
|
||||
keyword: string;
|
||||
isActive: boolean | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './tenants.component.html',
|
||||
animations: [appModuleAnimation()]
|
||||
})
|
||||
export class TenantsComponent extends PagedListingComponentBase<TenantDto> {
|
||||
tenants: TenantDto[] = [];
|
||||
keyword = '';
|
||||
isActive: boolean | null;
|
||||
advancedFiltersVisible = false;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _tenantService: TenantServiceProxy,
|
||||
private _modalService: BsModalService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
list(
|
||||
request: PagedTenantsRequestDto,
|
||||
pageNumber: number,
|
||||
finishedCallback: Function
|
||||
): void {
|
||||
request.keyword = this.keyword;
|
||||
request.isActive = this.isActive;
|
||||
|
||||
this._tenantService
|
||||
.getAll(
|
||||
request.keyword,
|
||||
request.isActive,
|
||||
request.skipCount,
|
||||
request.maxResultCount
|
||||
)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
finishedCallback();
|
||||
})
|
||||
)
|
||||
.subscribe((result: TenantDtoPagedResultDto) => {
|
||||
this.tenants = result.items;
|
||||
this.showPaging(result, pageNumber);
|
||||
});
|
||||
}
|
||||
|
||||
delete(tenant: TenantDto): void {
|
||||
abp.message.confirm(
|
||||
this.l('TenantDeleteWarningMessage', tenant.name),
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
this._tenantService
|
||||
.delete(tenant.id)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
abp.notify.success(this.l('SuccessfullyDeleted'));
|
||||
this.refresh();
|
||||
})
|
||||
)
|
||||
.subscribe(() => {});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createTenant(): void {
|
||||
this.showCreateOrEditTenantDialog();
|
||||
}
|
||||
|
||||
editTenant(tenant: TenantDto): void {
|
||||
this.showCreateOrEditTenantDialog(tenant.id);
|
||||
}
|
||||
|
||||
showCreateOrEditTenantDialog(id?: number): void {
|
||||
let createOrEditTenantDialog: BsModalRef;
|
||||
if (!id) {
|
||||
createOrEditTenantDialog = this._modalService.show(
|
||||
CreateTenantDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
createOrEditTenantDialog = this._modalService.show(
|
||||
EditTenantDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
initialState: {
|
||||
id: id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createOrEditTenantDialog.content.onSave.subscribe(() => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
clearFilters(): void {
|
||||
this.keyword = '';
|
||||
this.isActive = undefined;
|
||||
this.getDataPage(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h1>{{ "UpdatePassword" | localize }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#changePasswordForm="ngForm"
|
||||
(ngSubmit)="changePassword()"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="modal-body">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="currentPassword">
|
||||
{{ "CurrentPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="currentPassword"
|
||||
id="currentPassword"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="changePasswordDto.currentPassword"
|
||||
#currentPasswordModel="ngModel"
|
||||
#currentPasswordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="currentPasswordModel"
|
||||
[controlEl]="currentPasswordEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="newPassword">
|
||||
{{ "NewPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="newPassword"
|
||||
id="newPassword"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
validateEqual="confirmNewPassword"
|
||||
reverse="true"
|
||||
pattern="(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s)[0-9a-zA-Z!@#$%^&*()]*$"
|
||||
[(ngModel)]="changePasswordDto.newPassword"
|
||||
#newPasswordModel="ngModel"
|
||||
#newPasswordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="newPasswordModel"
|
||||
[controlEl]="newPasswordEl"
|
||||
[customValidationErrors]="newPasswordValidationErrors"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="confirmNewPassword">
|
||||
{{ "ConfirmNewPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="confirmNewPassword"
|
||||
id="confirmNewPassword"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
validateEqual="newPassword"
|
||||
reverse="false"
|
||||
ngModel
|
||||
#confirmNewPasswordModel="ngModel"
|
||||
#confirmNewPasswordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="confirmNewPasswordModel"
|
||||
[controlEl]="confirmNewPasswordEl"
|
||||
[customValidationErrors]="
|
||||
confirmNewPasswordValidationErrors
|
||||
"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer justify-content-between">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!changePasswordForm.form.valid || saving"
|
||||
>
|
||||
{{ "Save" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
@ -0,0 +1,58 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
ChangePasswordDto,
|
||||
UserServiceProxy
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { AbpValidationError } from '@shared/components/validation/abp-validation.api';
|
||||
|
||||
@Component({
|
||||
templateUrl: './change-password.component.html',
|
||||
animations: [appModuleAnimation()]
|
||||
})
|
||||
export class ChangePasswordComponent extends AppComponentBase {
|
||||
saving = false;
|
||||
changePasswordDto = new ChangePasswordDto();
|
||||
newPasswordValidationErrors: Partial<AbpValidationError>[] = [
|
||||
{
|
||||
name: 'pattern',
|
||||
localizationKey:
|
||||
'PasswordsMustBeAtLeast8CharactersContainLowercaseUppercaseNumber',
|
||||
},
|
||||
];
|
||||
confirmNewPasswordValidationErrors: Partial<AbpValidationError>[] = [
|
||||
{
|
||||
name: 'validateEqual',
|
||||
localizationKey: 'PasswordsDoNotMatch',
|
||||
},
|
||||
];
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private userServiceProxy: UserServiceProxy,
|
||||
private router: Router
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
changePassword() {
|
||||
this.saving = true;
|
||||
|
||||
this.userServiceProxy
|
||||
.changePassword(this.changePasswordDto)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.saving = false;
|
||||
})
|
||||
)
|
||||
.subscribe((success) => {
|
||||
if (success) {
|
||||
abp.message.success('Password changed successfully', 'Success');
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#createUserModal="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'CreateNewUser' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<tabset>
|
||||
<tab [heading]="'UserDetails' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="surname">
|
||||
{{ "Surname" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="surname"
|
||||
id="surname"
|
||||
required
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.surname"
|
||||
#surnameModel="ngModel"
|
||||
#surnameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="surnameModel"
|
||||
[controlEl]="surnameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="userName">
|
||||
{{ "UserName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="userName"
|
||||
id="userName"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.userName"
|
||||
#userNameModel="ngModel"
|
||||
#userNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="userNameModel"
|
||||
[controlEl]="userNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="password">
|
||||
{{ "Password" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
id="password"
|
||||
required
|
||||
maxlength="32"
|
||||
validateEqual="confirmPassword"
|
||||
reverse="true"
|
||||
pattern="(?=^.{8,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s)[0-9a-zA-Z!@#$%^&*()]*$"
|
||||
[(ngModel)]="user.password"
|
||||
#passwordModel="ngModel"
|
||||
#passwordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="passwordModel"
|
||||
[controlEl]="passwordEl"
|
||||
[customValidationErrors]="passwordValidationErrors"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="confirmPassword">
|
||||
{{ "ConfirmPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
name="confirmPassword"
|
||||
id="confirmPassword"
|
||||
required
|
||||
maxlength="32"
|
||||
validateEqual="password"
|
||||
reverse="false"
|
||||
ngModel
|
||||
#confirmPasswordModel="ngModel"
|
||||
#confirmPasswordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="confirmPasswordModel"
|
||||
[controlEl]="confirmPasswordEl"
|
||||
[customValidationErrors]="confirmPasswordValidationErrors"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="emailAddress">
|
||||
{{ "EmailAddress" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
name="emailAddress"
|
||||
id="emailAddress"
|
||||
required
|
||||
maxlength="256"
|
||||
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$"
|
||||
[(ngModel)]="user.emailAddress"
|
||||
#emailAddressModel="ngModel"
|
||||
#emailAddressEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="emailAddressModel"
|
||||
[controlEl]="emailAddressEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
name="isActive"
|
||||
id="isActive"
|
||||
[(ngModel)]="user.isActive"
|
||||
/>
|
||||
<label class="custom-control-label mt-2" for="isActive"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
<tab [heading]="'UserRoles' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row mb-0">
|
||||
<ng-container *ngFor="let role of roles; let i = index">
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
[id]="'role_' + i"
|
||||
[checked]="isRoleChecked(role.normalizedName)"
|
||||
(change)="onRoleChange(role, $event)"
|
||||
/>
|
||||
<label class="custom-control-label" [for]="'role_' + i">
|
||||
{{ role.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!createUserModal.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,105 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { forEach as _forEach, map as _map } from 'lodash-es';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
UserServiceProxy,
|
||||
CreateUserDto,
|
||||
RoleDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { AbpValidationError } from '@shared/components/validation/abp-validation.api';
|
||||
|
||||
@Component({
|
||||
templateUrl: './create-user-dialog.component.html'
|
||||
})
|
||||
export class CreateUserDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
user = new CreateUserDto();
|
||||
roles: RoleDto[] = [];
|
||||
checkedRolesMap: { [key: string]: boolean } = {};
|
||||
defaultRoleCheckedStatus = false;
|
||||
passwordValidationErrors: Partial<AbpValidationError>[] = [
|
||||
{
|
||||
name: 'pattern',
|
||||
localizationKey:
|
||||
'PasswordsMustBeAtLeast8CharactersContainLowercaseUppercaseNumber',
|
||||
},
|
||||
];
|
||||
confirmPasswordValidationErrors: Partial<AbpValidationError>[] = [
|
||||
{
|
||||
name: 'validateEqual',
|
||||
localizationKey: 'PasswordsDoNotMatch',
|
||||
},
|
||||
];
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
public _userService: UserServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.user.isActive = true;
|
||||
|
||||
this._userService.getRoles().subscribe((result) => {
|
||||
this.roles = result.items;
|
||||
this.setInitialRolesStatus();
|
||||
});
|
||||
}
|
||||
|
||||
setInitialRolesStatus(): void {
|
||||
_map(this.roles, (item) => {
|
||||
this.checkedRolesMap[item.normalizedName] = this.isRoleChecked(
|
||||
item.normalizedName
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
isRoleChecked(normalizedName: string): boolean {
|
||||
// just return default role checked status
|
||||
// it's better to use a setting
|
||||
return this.defaultRoleCheckedStatus;
|
||||
}
|
||||
|
||||
onRoleChange(role: RoleDto, $event) {
|
||||
this.checkedRolesMap[role.normalizedName] = $event.target.checked;
|
||||
}
|
||||
|
||||
getCheckedRoles(): string[] {
|
||||
const roles: string[] = [];
|
||||
_forEach(this.checkedRolesMap, function (value, key) {
|
||||
if (value) {
|
||||
roles.push(key);
|
||||
}
|
||||
});
|
||||
return roles;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
this.user.roleNames = this.getCheckedRoles();
|
||||
|
||||
this._userService.create(this.user).subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
149
angular/src/app/users/edit-user/edit-user-dialog.component.html
Normal file
149
angular/src/app/users/edit-user/edit-user-dialog.component.html
Normal file
@ -0,0 +1,149 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#editUserModal="ngForm"
|
||||
(ngSubmit)="save()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'EditUser' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<tabset>
|
||||
<tab [heading]="'UserDetails' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="name">
|
||||
{{ "Name" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.name"
|
||||
#nameModel="ngModel"
|
||||
#nameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="nameModel"
|
||||
[controlEl]="nameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="surname">
|
||||
{{ "Surname" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="surname"
|
||||
id="surname"
|
||||
required
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.surname"
|
||||
#surnameModel="ngModel"
|
||||
#surnameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="surnameModel"
|
||||
[controlEl]="surnameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="userName">
|
||||
{{ "UserName" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="userName"
|
||||
id="userName"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="32"
|
||||
[(ngModel)]="user.userName"
|
||||
#userNameModel="ngModel"
|
||||
#userNameEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="userNameModel"
|
||||
[controlEl]="userNameEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="emailAddress">
|
||||
{{ "EmailAddress" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
name="emailAddress"
|
||||
id="emailAddress"
|
||||
required
|
||||
maxlength="256"
|
||||
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$"
|
||||
[(ngModel)]="user.emailAddress"
|
||||
#emailAddressModel="ngModel"
|
||||
#emailAddressEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="emailAddressModel"
|
||||
[controlEl]="emailAddressEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
name="isActive"
|
||||
id="isActive"
|
||||
[(ngModel)]="user.isActive"
|
||||
/>
|
||||
<label class="custom-control-label mt-2" for="isActive"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
<tab [heading]="'UserRoles' | localize" class="pt-3 px-2">
|
||||
<div class="form-group row mb-0">
|
||||
<ng-container *ngFor="let role of roles; let i = index">
|
||||
<div class="col-md-6">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
[id]="'role_' + i"
|
||||
[checked]="isRoleChecked(role.normalizedName)"
|
||||
(change)="onRoleChange(role, $event)"
|
||||
/>
|
||||
<label class="custom-control-label" [for]="'role_' + i">
|
||||
{{ role.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="saving"
|
||||
[saveDisabled]="!editUserModal.form.valid || saving"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,91 @@
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { forEach as _forEach, includes as _includes, map as _map } from 'lodash-es';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
UserServiceProxy,
|
||||
UserDto,
|
||||
RoleDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
|
||||
@Component({
|
||||
templateUrl: './edit-user-dialog.component.html'
|
||||
})
|
||||
export class EditUserDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
saving = false;
|
||||
user = new UserDto();
|
||||
roles: RoleDto[] = [];
|
||||
checkedRolesMap: { [key: string]: boolean } = {};
|
||||
id: number;
|
||||
|
||||
@Output() onSave = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
public _userService: UserServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._userService.get(this.id).subscribe((result) => {
|
||||
this.user = result;
|
||||
|
||||
this._userService.getRoles().subscribe((result2) => {
|
||||
this.roles = result2.items;
|
||||
this.setInitialRolesStatus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setInitialRolesStatus(): void {
|
||||
_map(this.roles, (item) => {
|
||||
this.checkedRolesMap[item.normalizedName] = this.isRoleChecked(
|
||||
item.normalizedName
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
isRoleChecked(normalizedName: string): boolean {
|
||||
return _includes(this.user.roleNames, normalizedName);
|
||||
}
|
||||
|
||||
onRoleChange(role: RoleDto, $event) {
|
||||
this.checkedRolesMap[role.normalizedName] = $event.target.checked;
|
||||
}
|
||||
|
||||
getCheckedRoles(): string[] {
|
||||
const roles: string[] = [];
|
||||
_forEach(this.checkedRolesMap, function (value, key) {
|
||||
if (value) {
|
||||
roles.push(key);
|
||||
}
|
||||
});
|
||||
return roles;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.saving = true;
|
||||
|
||||
this.user.roleNames = this.getCheckedRoles();
|
||||
|
||||
this._userService.update(this.user).subscribe(
|
||||
() => {
|
||||
this.notify.info(this.l('SavedSuccessfully'));
|
||||
this.bsModalRef.hide();
|
||||
this.onSave.emit();
|
||||
},
|
||||
() => {
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<form
|
||||
class="form-horizontal"
|
||||
autocomplete="off"
|
||||
#resetPasswordModal="ngForm"
|
||||
(ngSubmit)="resetPassword()"
|
||||
>
|
||||
<abp-modal-header
|
||||
[title]="'ResetPassword' | localize"
|
||||
(onCloseClick)="bsModalRef.hide()"
|
||||
></abp-modal-header>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-9 offset-md-3">
|
||||
<p class="text-info mb-1">
|
||||
{{ "ResetPasswordStepOneInfo" | localize }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
<label class="col-md-3 col-form-label" for="adminPassword">
|
||||
{{ "AdminPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="adminPassword"
|
||||
name="adminPassword"
|
||||
required
|
||||
[(ngModel)]="resetPasswordDto.adminPassword"
|
||||
#adminPasswordModel="ngModel"
|
||||
#adminPasswordEl
|
||||
/>
|
||||
<abp-validation-summary
|
||||
[control]="adminPasswordModel"
|
||||
[controlEl]="adminPasswordEl"
|
||||
></abp-validation-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9 offset-md-3">
|
||||
<p class="text-info mb-1">
|
||||
{{ "ResetPasswordStepTwoInfo" | localize }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="newPassword">
|
||||
{{ "NewPassword" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="NewPassword"
|
||||
id="newPassword"
|
||||
readonly
|
||||
[ngModel]="resetPasswordDto.newPassword"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<abp-modal-footer
|
||||
[cancelDisabled]="isLoading"
|
||||
[saveDisabled]="!resetPasswordModal.form.valid || isLoading"
|
||||
(onCancelClick)="bsModalRef.hide()"
|
||||
></abp-modal-footer>
|
||||
</form>
|
@ -0,0 +1,49 @@
|
||||
import { Component, OnInit, Injector } from '@angular/core';
|
||||
import { AppComponentBase } from '@shared/app-component-base';
|
||||
import {
|
||||
UserServiceProxy,
|
||||
ResetPasswordDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-password',
|
||||
templateUrl: './reset-password.component.html'
|
||||
})
|
||||
export class ResetPasswordDialogComponent extends AppComponentBase
|
||||
implements OnInit {
|
||||
public isLoading = false;
|
||||
public resetPasswordDto: ResetPasswordDto;
|
||||
id: number;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _userService: UserServiceProxy,
|
||||
public bsModalRef: BsModalRef
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.isLoading = true;
|
||||
this.resetPasswordDto = new ResetPasswordDto();
|
||||
this.resetPasswordDto.userId = this.id;
|
||||
this.resetPasswordDto.newPassword = Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 10);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
public resetPassword(): void {
|
||||
this.isLoading = true;
|
||||
this._userService.resetPassword(this.resetPasswordDto).subscribe(
|
||||
() => {
|
||||
this.notify.info('Password Reset');
|
||||
this.bsModalRef.hide();
|
||||
},
|
||||
() => {
|
||||
this.isLoading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
234
angular/src/app/users/users.component.html
Normal file
234
angular/src/app/users/users.component.html
Normal file
@ -0,0 +1,234 @@
|
||||
<div [@routerTransition]>
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h1>{{ "Users" | localize }}</h1>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<a href="javascript:;" class="btn bg-blue" (click)="createUser()">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
{{ "Create" | localize }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="content px-2">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md-6"> </div>
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button
|
||||
type="button"
|
||||
class="btn bg-blue"
|
||||
(click)="getDataPage(1)"
|
||||
>
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="keyword"
|
||||
[placeholder]="'SearchWithThreeDot' | localize"
|
||||
[(ngModel)]="keyword"
|
||||
(keyup.enter)="getDataPage(1)"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
(click)="advancedFiltersVisible = !advancedFiltersVisible"
|
||||
>
|
||||
<i
|
||||
class="fas"
|
||||
[class.fa-angle-up]="advancedFiltersVisible"
|
||||
[class.fa-angle-down]="!advancedFiltersVisible"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="advancedFiltersVisible" class="card mb-0 mt-1">
|
||||
<div class="card-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group row mb-0">
|
||||
<label class="col-md-3 col-form-label">
|
||||
{{ "IsActive" | localize }}
|
||||
</label>
|
||||
<div class="col-md-9 pt-2">
|
||||
<div class="custom-control custom-radio d-inline">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActiveAll"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="undefined"
|
||||
checked
|
||||
/>
|
||||
<label class="custom-control-label" for="isActiveAll">
|
||||
{{ "All" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio d-inline mx-3">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActiveActive"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="true"
|
||||
/>
|
||||
<label
|
||||
class="custom-control-label"
|
||||
for="isActiveActive"
|
||||
>
|
||||
{{ "Yes" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio d-inline">
|
||||
<input
|
||||
type="radio"
|
||||
class="custom-control-input"
|
||||
id="isActivePassive"
|
||||
name="isActive"
|
||||
[(ngModel)]="isActive"
|
||||
[value]="false"
|
||||
/>
|
||||
<label
|
||||
class="custom-control-label"
|
||||
for="isActivePassive"
|
||||
>
|
||||
{{ "No" | localize }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn bg-blue"
|
||||
(click)="getDataPage(1)"
|
||||
>
|
||||
{{ "Search" | localize }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default float-right"
|
||||
(click)="clearFilters()"
|
||||
>
|
||||
{{ "Clear" | localize }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered" [busy]="isTableLoading">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{{ "UserName" | localize }}</th>
|
||||
<th>{{ "FullName" | localize }}</th>
|
||||
<th>{{ "EmailAddress" | localize }}</th>
|
||||
<th>{{ "IsActive" | localize }}</th>
|
||||
<th style="width: 310px;">{{ "Actions" | localize }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
*ngFor="
|
||||
let user of users
|
||||
| paginate
|
||||
: {
|
||||
id: 'server',
|
||||
itemsPerPage: pageSize,
|
||||
currentPage: pageNumber,
|
||||
totalItems: totalItems
|
||||
}
|
||||
"
|
||||
>
|
||||
<td>{{ user.userName }}</td>
|
||||
<td>{{ user.fullName }}</td>
|
||||
<td>{{ user.emailAddress }}</td>
|
||||
<td>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
disabled
|
||||
[checked]="user.isActive"
|
||||
/>
|
||||
<label class="custom-control-label"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-secondary"
|
||||
(click)="editUser(user)"
|
||||
>
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
{{ "Edit" | localize }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-danger mx-2"
|
||||
(click)="delete(user)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
{{ "Delete" | localize }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-secondary"
|
||||
(click)="resetPassword(user)"
|
||||
>
|
||||
<i class="fas fa-lock"></i>
|
||||
{{ "ResetPassword" | localize }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer table-card-footer bg-light border-top">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-12 text-sm-left text-center">
|
||||
<button class="btn btn-secondary" (click)="refresh()">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12 text-center">
|
||||
<p class="mb-0 my-2">
|
||||
{{ "TotalRecordsCount" | localize: totalItems }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-4 col-12">
|
||||
<div class="float-sm-right m-auto">
|
||||
<abp-pagination-controls
|
||||
id="server"
|
||||
(pageChange)="getDataPage($event)"
|
||||
>
|
||||
</abp-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
134
angular/src/app/users/users.component.ts
Normal file
134
angular/src/app/users/users.component.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { appModuleAnimation } from '@shared/animations/routerTransition';
|
||||
import {
|
||||
PagedListingComponentBase,
|
||||
PagedRequestDto
|
||||
} from 'shared/paged-listing-component-base';
|
||||
import {
|
||||
UserServiceProxy,
|
||||
UserDto,
|
||||
UserDtoPagedResultDto
|
||||
} from '@shared/service-proxies/service-proxies';
|
||||
import { CreateUserDialogComponent } from './create-user/create-user-dialog.component';
|
||||
import { EditUserDialogComponent } from './edit-user/edit-user-dialog.component';
|
||||
import { ResetPasswordDialogComponent } from './reset-password/reset-password.component';
|
||||
|
||||
class PagedUsersRequestDto extends PagedRequestDto {
|
||||
keyword: string;
|
||||
isActive: boolean | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './users.component.html',
|
||||
animations: [appModuleAnimation()]
|
||||
})
|
||||
export class UsersComponent extends PagedListingComponentBase<UserDto> {
|
||||
users: UserDto[] = [];
|
||||
keyword = '';
|
||||
isActive: boolean | null;
|
||||
advancedFiltersVisible = false;
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private _userService: UserServiceProxy,
|
||||
private _modalService: BsModalService
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
createUser(): void {
|
||||
this.showCreateOrEditUserDialog();
|
||||
}
|
||||
|
||||
editUser(user: UserDto): void {
|
||||
this.showCreateOrEditUserDialog(user.id);
|
||||
}
|
||||
|
||||
public resetPassword(user: UserDto): void {
|
||||
this.showResetPasswordUserDialog(user.id);
|
||||
}
|
||||
|
||||
clearFilters(): void {
|
||||
this.keyword = '';
|
||||
this.isActive = undefined;
|
||||
this.getDataPage(1);
|
||||
}
|
||||
|
||||
protected list(
|
||||
request: PagedUsersRequestDto,
|
||||
pageNumber: number,
|
||||
finishedCallback: Function
|
||||
): void {
|
||||
request.keyword = this.keyword;
|
||||
request.isActive = this.isActive;
|
||||
|
||||
this._userService
|
||||
.getAll(
|
||||
request.keyword,
|
||||
request.isActive,
|
||||
request.skipCount,
|
||||
request.maxResultCount
|
||||
)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
finishedCallback();
|
||||
})
|
||||
)
|
||||
.subscribe((result: UserDtoPagedResultDto) => {
|
||||
this.users = result.items;
|
||||
this.showPaging(result, pageNumber);
|
||||
});
|
||||
}
|
||||
|
||||
protected delete(user: UserDto): void {
|
||||
abp.message.confirm(
|
||||
this.l('UserDeleteWarningMessage', user.fullName),
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
this._userService.delete(user.id).subscribe(() => {
|
||||
abp.notify.success(this.l('SuccessfullyDeleted'));
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private showResetPasswordUserDialog(id?: number): void {
|
||||
this._modalService.show(ResetPasswordDialogComponent, {
|
||||
class: 'modal-lg',
|
||||
initialState: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private showCreateOrEditUserDialog(id?: number): void {
|
||||
let createOrEditUserDialog: BsModalRef;
|
||||
if (!id) {
|
||||
createOrEditUserDialog = this._modalService.show(
|
||||
CreateUserDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
createOrEditUserDialog = this._modalService.show(
|
||||
EditUserDialogComponent,
|
||||
{
|
||||
class: 'modal-lg',
|
||||
initialState: {
|
||||
id: id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createOrEditUserDialog.content.onSave.subscribe(() => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
0
angular/src/assets/.gitkeep
Normal file
0
angular/src/assets/.gitkeep
Normal file
24
angular/src/assets/abp-web-resources/abp.freeze-ui.js
Normal file
24
angular/src/assets/abp-web-resources/abp.freeze-ui.js
Normal file
@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
var abp = abp || {};
|
||||
|
||||
(function () {
|
||||
if (!FreezeUI || !UnFreezeUI) {
|
||||
return;
|
||||
}
|
||||
|
||||
abp.ui.setBusy = function (elm, text, delay) {
|
||||
FreezeUI({
|
||||
element: elm,
|
||||
text: text ? text : " ",
|
||||
delay: delay
|
||||
});
|
||||
};
|
||||
|
||||
abp.ui.clearBusy = function (elm, delay) {
|
||||
UnFreezeUI({
|
||||
element: elm,
|
||||
delay: delay
|
||||
});
|
||||
};
|
||||
})();
|
128
angular/src/assets/abp-web-resources/abp.sweet-alert.js
Normal file
128
angular/src/assets/abp-web-resources/abp.sweet-alert.js
Normal file
@ -0,0 +1,128 @@
|
||||
var abp = abp || {};
|
||||
|
||||
(function () {
|
||||
if (!Swal) {
|
||||
return;
|
||||
}
|
||||
/* MESSAGE **************************************************/
|
||||
|
||||
|
||||
var showMessage = function showMessage(type, message, title, isHtml, options) {
|
||||
if (!title) {
|
||||
title = message;
|
||||
message = undefined;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.title = title;
|
||||
options.icon = type;
|
||||
options.confirmButtonText = options.confirmButtonText || abp.localization.abpWeb("Ok");
|
||||
|
||||
if (isHtml) {
|
||||
options.html = message;
|
||||
} else {
|
||||
options.text = message;
|
||||
}
|
||||
|
||||
return Swal.fire(options);
|
||||
};
|
||||
|
||||
abp.message.info = function (message, title, isHtml, options) {
|
||||
return showMessage("info", message, title, isHtml, options);
|
||||
};
|
||||
|
||||
abp.message.success = function (message, title, isHtml, options) {
|
||||
return showMessage("success", message, title, isHtml, options);
|
||||
};
|
||||
|
||||
abp.message.warn = function (message, title, isHtml, options) {
|
||||
return showMessage("warning", message, title, isHtml, options);
|
||||
};
|
||||
|
||||
abp.message.error = function (message, title, isHtml, options) {
|
||||
return showMessage("error", message, title, isHtml, options);
|
||||
};
|
||||
|
||||
abp.message.confirm = function (message, titleOrCallback, callback, isHtml, options) {
|
||||
var title = undefined;
|
||||
|
||||
if (typeof titleOrCallback === "function") {
|
||||
callback = titleOrCallback;
|
||||
} else if (titleOrCallback) {
|
||||
title = titleOrCallback;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.title = title ? title : abp.localization.abpWeb("AreYouSure");
|
||||
options.icon = "warning";
|
||||
options.confirmButtonText = options.confirmButtonText || abp.localization.abpWeb("Yes");
|
||||
options.cancelButtonText = options.cancelButtonText || abp.localization.abpWeb("Cancel");
|
||||
options.showCancelButton = true;
|
||||
|
||||
if (isHtml) {
|
||||
options.html = message;
|
||||
} else {
|
||||
options.text = message;
|
||||
}
|
||||
|
||||
return Swal.fire(options).then(function (result) {
|
||||
callback && callback(result.value);
|
||||
});
|
||||
};
|
||||
/* NOTIFICATION *********************************************/
|
||||
|
||||
|
||||
var Toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: "bottom-end",
|
||||
showConfirmButton: false,
|
||||
timer: 3000
|
||||
});
|
||||
|
||||
var showNotification = function showNotification(type, message, title, options) {
|
||||
var icon = options.customClass.icon ? "<i class=\"mr-2 text-light ".concat(options.customClass.icon, "\"></i>") : "";
|
||||
|
||||
if (title) {
|
||||
options.title = "".concat(icon, "<span class=\"text-light\">").concat(title, "</span>");
|
||||
}
|
||||
|
||||
options.html = "".concat(title ? "" : icon, "\n <span class=\"text-light\">").concat(message, "</span>");
|
||||
Toast.fire(options);
|
||||
};
|
||||
|
||||
abp.notify.success = function (message, title, options) {
|
||||
showNotification("success", message, title, Object.assign({
|
||||
background: "#34bfa3",
|
||||
customClass: {
|
||||
icon: "fas fa-check-circle"
|
||||
}
|
||||
}, options));
|
||||
};
|
||||
|
||||
abp.notify.info = function (message, title, options) {
|
||||
showNotification("info", message, title, Object.assign({
|
||||
background: "#36a3f7",
|
||||
customClass: {
|
||||
icon: "fas fa-info-circle"
|
||||
}
|
||||
}, options));
|
||||
};
|
||||
|
||||
abp.notify.warn = function (message, title, options) {
|
||||
showNotification("warning", message, title, Object.assign({
|
||||
background: "#ffb822",
|
||||
customClass: {
|
||||
icon: "fas fa-exclamation-triangle"
|
||||
}
|
||||
}, options));
|
||||
};
|
||||
|
||||
abp.notify.error = function (message, title, options) {
|
||||
showNotification("error", message, title, Object.assign({
|
||||
background: "#f4516c",
|
||||
customClass: {
|
||||
icon: "fas fa-exclamation-circle"
|
||||
}
|
||||
}, options));
|
||||
};
|
||||
})();
|
18
angular/src/assets/appconfig.json
Normal file
18
angular/src/assets/appconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"remoteServiceBaseUrl": "https://localhost:44311",
|
||||
"appBaseUrl": "http://localhost:4200",
|
||||
"localeMappings": [
|
||||
{
|
||||
"from": "pt-BR",
|
||||
"to": "pt"
|
||||
},
|
||||
{
|
||||
"from": "zh-CN",
|
||||
"to": "zh"
|
||||
},
|
||||
{
|
||||
"from": "he-IL",
|
||||
"to": "he"
|
||||
}
|
||||
]
|
||||
}
|
18
angular/src/assets/appconfig.production.json
Normal file
18
angular/src/assets/appconfig.production.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"remoteServiceBaseUrl": "https://localhost:44311",
|
||||
"appBaseUrl": "http://localhost:4200",
|
||||
"localeMappings": [
|
||||
{
|
||||
"from": "pt-BR",
|
||||
"to": "pt"
|
||||
},
|
||||
{
|
||||
"from": "zh-CN",
|
||||
"to": "zh"
|
||||
},
|
||||
{
|
||||
"from": "he-IL",
|
||||
"to": "he"
|
||||
}
|
||||
]
|
||||
}
|
56
angular/src/assets/freeze-ui/freeze-ui.css
Normal file
56
angular/src/assets/freeze-ui/freeze-ui.css
Normal file
@ -0,0 +1,56 @@
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: translateZ(0) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateZ(0) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.freeze-ui {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999999999;
|
||||
background-color: #fff;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.freeze-ui.is-unfreezing {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.freeze-ui:after {
|
||||
content: attr(data-text);
|
||||
display: block;
|
||||
max-width: 125px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 20px;
|
||||
font-family: sans-serif;
|
||||
color: #343a40;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.freeze-ui:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
border-radius: 50%;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: transparent #228ae6 #228ae6;
|
||||
position: absolute;
|
||||
top: calc(50% - 75px);
|
||||
left: calc(50% - 75px);
|
||||
will-change: transform;
|
||||
animation: spin 0.75s infinite ease-in-out;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user