Create Boiler Template project

This commit is contained in:
sanjoy 2022-08-05 11:19:02 +05:30
commit 35ebc530c4
381 changed files with 72770 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
**/wwwroot/lib/** linguist-vendored
**/wwwroot/css/** linguist-vendored

9
.gitignore vendored Normal file
View 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
View 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
View 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
![](_screenshots/module-zero-core-template-ui-home.png)
#### User Creation Modal
![](_screenshots/module-zero-core-template-ui-user-create-modal.png)
#### Login Page
![](_screenshots/module-zero-core-template-ui-login.png)
# 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
_screenshots/ui-login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

12
angular/.browserslistrc Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
FROM nginx
COPY . /usr/share/nginx/html

View 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>

View 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

View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
<configuration>
<runtime>
<gcServer enabled="true"/>
</runtime>
</configuration>

View 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
View 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
View 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
View 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
});
};

View File

@ -0,0 +1 @@
"..\node_modules\.bin\nswag" run

View 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
View 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": {}
}

View 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());
}
};

View 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 { }

View 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>

View 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');
}
}

View 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 {
}

View File

@ -0,0 +1,9 @@
<div class="row">
<div class="col-md-12 text-center">
<small>
Copyright &copy; {{ currentYear }}
<b class="ml-2">{{ "Version" | localize }}</b>
{{ versionText }}
</small>
</div>
</div>

View 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') +
']';
}
}

View File

@ -0,0 +1,3 @@
<div class="login-logo">
<a href="/"><b>MeetingSchedule</b></a>
</div>

View 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 {}

View 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>

View 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();
}
}

View 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>

View 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));
}
}

View 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>

View 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;
});
});
}
}

View File

@ -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>

View 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;
}
);
}
}

View 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>

View 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;
}
}
}

View 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();
});
}
}

View 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>

View 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);
}
}

View 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 { }

View 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>

View 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();
}));
});

View 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);
}
}

View 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 {}

View 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>

View 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);
}
}

View File

@ -0,0 +1,9 @@
<footer class="main-footer">
<strong>
Copyright &copy; {{ currentYear }}
<a href="javascript:;">MeetingSchedule</a>.
</strong>
<div class="float-right d-none d-sm-inline-block">
<b>{{ "Version" | localize }} </b> {{ versionText }}
</div>
</footer>

View 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') +
']';
}
}

View 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>

View 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();
});
}
}

View 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>

View 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);
}
}

View 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>

View 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();
}
}

View 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>

View 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 {}

View 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>

View 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 {}

View 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>

View 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);
}
}

View 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>

View 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();
}
}

View 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>

View 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');
}
}

View File

@ -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>

View File

@ -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;
}
);
}
}

View 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>

View File

@ -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;
}
);
}
}

View 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">&emsp;</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>

View 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();
});
}
}

View File

@ -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>

View File

@ -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;
}
);
}
}

View File

@ -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>

View File

@ -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;
}
);
}
}

View 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">&emsp;</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>

View 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);
}
}

View File

@ -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>

View File

@ -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(['/']);
}
});
}
}

View File

@ -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>

View File

@ -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;
}
);
}
}

View 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>

View File

@ -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;
}
);
}
}

View File

@ -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>

View File

@ -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;
}
);
}
}

View 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">&emsp;</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>

View 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();
});
}
}

View File

View 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
});
};
})();

View 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));
};
})();

View 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"
}
]
}

View 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"
}
]
}

View 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