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