Commit 8a9e379b authored by ctorres's avatar ctorres

Initial commit

parents
Pipeline #315 failed with stages
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
FROM nginx:1.17-alpine
LABEL maintainer "mortiz@bytesw.com"
RUN apk --no-cache add curl
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst && \
chmod +x envsubst && \
mv envsubst /usr/local/bin
EXPOSE 80
COPY ./nginx.config /etc/nginx/nginx.template
CMD ["/bin/sh", "-c", "envsubst < /etc/nginx/nginx.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]
COPY dist/bytebot-html /usr/share/nginx/html
pipeline {
environment {
imagename = "bytesw/bytebot-html"
url = "http://192.168.27.148:5000"
tag_name = "192.168.27.148:5000"
credentials = "admin-docker-hub"
dockerImage = ''
PACKAGE_VERSION = '2.5.0'
}
agent any
parameters {
string(defaultValue: 'no', description: 'Ejecuta los procesos docker', name: 'include_docker')
}
stages {
stage('Install') {
steps {
echo "Branch is ${env.BRANCH_NAME}..."
nodejs(nodeJSInstallationName: 'NodeJS13', configId: '77600a18-f968-4cca-83e4-a9f76d165336') {
sh 'PACKAGE_VERSION=$(node -p -e "require(\'./package.json\').version")'
sh 'npm config ls'
sh 'npm install'
sh 'npm i -D typescript@3.4.3'
}
}
}
stage('Test') {
parallel {
stage('Static code analysis') {
steps {
nodejs(nodeJSInstallationName: 'NodeJS13', configId: '77600a18-f968-4cca-83e4-a9f76d165336') {
sh 'npm run-script lint'
}
}
}
}
}
stage('Build') {
steps {
nodejs(nodeJSInstallationName: 'NodeJS13', configId: '77600a18-f968-4cca-83e4-a9f76d165336') {
sh 'ng build'
}
}
}
stage('Build Docker Image') {
when {
expression { params.include_docker == 'yes' }
}
steps {
nodejs(nodeJSInstallationName: 'NodeJS13', configId: '77600a18-f968-4cca-83e4-a9f76d165336') {
sh 'PACKAGE_VERSION=$(node -p -e "require(\'./package.json\').version")'
}
script {
dockerImage = docker.build imagename
}
}
}
stage('Deploy Image') {
when {
expression { params.include_docker == 'yes' }
}
steps{
script {
docker.withRegistry(url, credentials ) {
dockerImage.push("$PACKAGE_VERSION")
dockerImage.push('latest')
}
}
}
}
stage('Remove Unused docker image') {
when {
expression { params.include_docker == 'yes' }
}
steps{
sh "docker rmi $tag_name/$imagename:$PACKAGE_VERSION"
sh "docker rmi $tag_name/$imagename:latest"
}
}
}
post {
always {
echo 'Confirmación de ejecución!'
emailext body: "${currentBuild.currentResult}: Job ${env.JOB_NAME} build ${env.BUILD_NUMBER}\n More info at: ${env.BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']],
subject: "Jenkins Build ${currentBuild.currentResult}: Job ${env.JOB_NAME}",
to: '$DEFAULT_RECIPIENTS'
}
}
}
# ByteBot
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.24.
## 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|guard|interface|enum|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/).
## 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).
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"bytebot-html": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "projects/bytebot-html",
"sourceRoot": "projects/bytebot-html/src",
"prefix": "byte",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/bytebot-html",
"index": "projects/bytebot-html/src/index.html",
"main": "projects/bytebot-html/src/main.ts",
"polyfills": "projects/bytebot-html/src/polyfills.ts",
"tsConfig": "projects/bytebot-html/tsconfig.app.json",
"aot": false,
"assets": [
"projects/bytebot-html/src/favicon.ico",
"projects/bytebot-html/src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"./node_modules/material-icons/iconfont/material-icons.css",
"projects/bytebot-html/src/styles.scss",
"node_modules/font-awesome/scss/font-awesome.scss",
"node_modules/ngx-toastr/toastr.css",
"node_modules/ngx-bootstrap/datepicker/bs-datepicker.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"vendor/metismenu/metisMenu.js",
"node_modules/popper.js/dist/umd/popper.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js",
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
"node_modules/jquery-slimscroll/jquery.slimscroll.min.js",
"node_modules/marked/marked.min.js",
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/mode-markdown.js",
"node_modules/ace-builds/src-min/theme-eclipse.js",
"node_modules/ace-builds/src-min/mode-java.js",
"node_modules/peity/jquery.peity.min.js",
"node_modules/pace-js/pace.min.js",
"vendor/inspinia/inspinia.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "projects/bytebot-html/src/environments/environment.ts",
"with": "projects/bytebot-html/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "bytebot-html:build"
},
"configurations": {
"production": {
"browserTarget": "bytebot-html:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "bytebot-html:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/bytebot-html/src/test.ts",
"polyfills": "projects/bytebot-html/src/polyfills.ts",
"tsConfig": "projects/bytebot-html/tsconfig.spec.json",
"karmaConfig": "projects/bytebot-html/karma.conf.js",
"assets": [
"projects/bytebot-html/src/favicon.ico",
"projects/bytebot-html/src/assets"
],
"styles": [
"projects/bytebot-html/src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/bytebot-html/tsconfig.app.json",
"projects/bytebot-html/tsconfig.spec.json",
"projects/bytebot-html/e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "projects/bytebot-html/e2e/protractor.conf.js",
"devServerTarget": "bytebot-html:serve"
},
"configurations": {
"production": {
"devServerTarget": "bytebot-html:serve:production"
}
}
}
}
}
},
"defaultProject": "bytebot-html"
}
\ No newline at end of file
server {
listen ${PORT:-80};
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $$uri /index.html;
}
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; connect-src 'self' https://*.okta.com https://*.herokuapp.com";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Feature-Policy "accelerometer 'none'; camera 'none'; microphone 'none'";
}
\ No newline at end of file
This diff is collapsed.
{
"name": "bytebot-projects",
"version": "1.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@agm/core": "^1.1.0",
"@angular-material-components/datetime-picker": "^2.0.4",
"@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/core": "~8.2.14",
"@angular/forms": "~8.2.14",
"@angular/material": "^8.2.3",
"@angular/material-moment-adapter": "^9.2.0",
"@angular/platform-browser": "^8.2.14",
"@angular/platform-browser-dynamic": "^8.2.14",
"@angular/router": "~8.2.14",
"@babel/compat-data": "^7.8.0",
"@ng-bootstrap/ng-bootstrap": "^5.1.5",
"@ng-select/ng-select": "^3.7.3",
"@ngx-translate/core": "^12.1.2",
"@ngx-translate/http-loader": "^4.0.0",
"@swimlane/ngx-datatable": "^16.0.3",
"@swimlane/ngx-graph": "^6.2.0",
"@types/jquery": "^3.3.34",
"@xdf/commons": "^1.0.4",
"@xdf/gallery": "^1.0.18",
"@xdf/graph": "file:libs/xdf-graph-0.0.1.tgz",
"@xdf/layouts": "^1.0.3",
"@xdf/security": "^1.0.0",
"@xdf/settings": "^1.0.8",
"ace-builds": "^1.4.8",
"angular-gauge-chart": "^0.7.2",
"angular-highcharts": "^9.0.2",
"angular-marked": "^1.2.2",
"bootstrap": "^4.4.1",
"chart.js": "^2.9.3",
"dagre": "^0.8.5",
"date-fns": "^2.11.1",
"font-awesome": "^4.7.0",
"hammerjs": "^2.0.8",
"highcharts": "^8.0.4",
"jquery": "^3.4.1",
"jquery-slimscroll": "^1.3.8",
"material-design-icons": "^3.0.1",
"material-icons": "^0.3.1",
"metismenu": "^3.0.6",
"moment": "^2.22.1",
"moment-timezone": "^0.5.31",
"net": "^1.0.2",
"ng-circle-progress": "^1.5.1",
"ng-json-view": "^3.0.0",
"ng2-ace-editor": "^0.3.9",
"ng2-charts": "^2.3.0",
"ng2-data-table": "^1.4.0",
"ngx-bootstrap": "^4.3.0",
"ngx-color": "^4.1.1",
"ngx-echarts": "^4.2.2",
"ngx-material-file-input": "2.0.0",
"ngx-moment": "^3.5.0",
"ngx-scrollbar": "7.1.0",
"ngx-slick-carousel": "^0.5.1",
"ngx-toastr": "^10.0.4",
"ngx-translate-multi-http-loader": "^3.0.0",
"ngx-tribute": "^1.5.1",
"nomnoml": "^0.6.2",
"pace-js": "^1.0.2",
"peity": "^3.3.0",
"popper": "^1.0.1",
"popper.js": "^1.16.1",
"rxjs": "~6.4.0",
"stompjs": "^2.3.3",
"tributejs": "^5.1.3",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.25",
"@angular/cli": "~8.3.24",
"@angular/compiler-cli": "~8.2.14",
"@angular/language-service": "~8.2.14",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}
# 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'.
\ No newline at end of file
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
\ No newline at end of file
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('bytebot-html app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/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-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/bytebot-html'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { CustomLayoutComponent, NotFoundComponent } from "@xdf/layouts";
import { AuthGuard, LoginComponent } from "@xdf/security";
import { BytebotLayoutComponent } from "./modules/bytebot-layout/bytebot-layout/bytebot-layout.component";
import { HomeComponent } from "./views/home/home.component";
const routes: Routes = [
// Remover para SSO
{ path: "login", component: LoginComponent },
// Main redirect
{ path: "", redirectTo: "home", pathMatch: "full", canActivate: [AuthGuard] },
{
path: "",
component: CustomLayoutComponent,
children: [
{ path: "home", component: HomeComponent, data: { breadcrumb: "Home" } },
{
path: "security",
data: { breadcrumb: "Seguridad" },
canLoad: [AuthGuard],
loadChildren: () =>
import("./modules/security/security.module").then(
(m) => m.SecurityModule
),
},
{
path: "settings",
data: { breadcrumb: "Ajustes Generales" },
canLoad: [AuthGuard],
loadChildren: () =>
import("./modules/settings/settings.module").then(
(m) => m.SettingsModule
),
},
{
path: "configuration",
data: { breadcrumb: "Agentes" },
canLoad: [AuthGuard],
loadChildren: () =>
import("./modules/agent/agent.module").then((m) => m.AgentModule),
},
{
path: "reserva",
data: { breadcrumb: "Aeropuertos" },
canLoad: [AuthGuard],
loadChildren: () =>
import("./modules/aeropuerto/aeropuerto.module").then(
(m) => m.AeropuertoModule
),
},
],
canActivate: [AuthGuard],
},
{ path: "notpermitted", component: NotFoundComponent },
// Handle all other routes
{ path: "**", component: NotFoundComponent },
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: "enabled",
anchorScrolling: "enabled",
onSameUrlNavigation: "ignore",
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
<router-outlet></router-outlet>
\ No newline at end of file
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'bytebot-html'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('bytebot-html');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('bytebot-html app is running!');
});
});
import { Component } from '@angular/core';
@Component({
selector: 'byte-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'bytebot-html';
}
import { BrowserModule } from "@angular/platform-browser";
import { NgModule, APP_INITIALIZER } from "@angular/core";
import { AppRoutingModule } from "./app-routing.module";
import {
CommonModule,
HashLocationStrategy,
LocationStrategy,
registerLocaleData,
} from "@angular/common";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import {
HttpClientModule,
HttpClient,
HTTP_INTERCEPTORS,
} from "@angular/common/http";
import {
TranslateModule,
TranslateLoader,
TranslateService,
} from "@ngx-translate/core";
// XDF
import {
XdfLayoutsModule,
SettingsService,
ByteSettingsService,
SettingsFakeBackendInterceptor,
} from "@xdf/layouts";
import {
XdfSecurityModule,
ByteAuthenticationService,
AuthGuard,
OAuthGuard,
OAuthAuthenticationService,
} from "@xdf/security";
import {
ResourceAuthGuard,
AuthenticationFakeBackendInterceptor,
ProgramsFakeBackendInterceptor,
} from "@xdf/security";
import {
XdfCommonsModule,
NotificationService,
ToastNotificationService,
AuthenticationService,
} from "@xdf/commons";
import {
INITSERVICE_OPTIONS,
InitCommonsService,
ErrorsHandler,
} from "@xdf/commons";
// Components
import { AppComponent } from "./app.component";
import { HomeComponent } from "./views/home/home.component";
import { ToastrModule } from "ngx-toastr";
import localeEs from "@angular/common/locales/es-PE";
import localeEn from "@angular/common/locales/en";
import {
MAT_DATE_LOCALE,
MatDialog,
MatButtonModule,
MatDialogModule,
MatPaginatorIntl,
DateAdapter,
} from "@angular/material";
import { MAT_DATE_FORMATS, MatIconModule } from "@angular/material";
import {
XdfGalleryModule,
CustomMatPaginatorIntl,
DATERANGEPICKER_LOCALE,
DaterangepickerLocaleService,
} from "@xdf/gallery";
import {
ConflictErrorDialogService,
HttpErrorHandleInterceptor,
} from "@xdf/gallery";
import { BytebotSettingsService } from "./services/bytebot-settings-service";
import { MultiTranslateHttpLoader } from "ngx-translate-multi-http-loader";
import { AgentModule } from "./modules/agent/agent.module";
import {
MAT_MOMENT_DATE_FORMATS,
MomentDateAdapter,
MAT_MOMENT_DATE_ADAPTER_OPTIONS,
} from "@angular/material-moment-adapter";
import { CustomProgramsFakeBackendInterceptor } from "./interceptors/custom-programs-fake-backend.interceptor";
import { AgentFakeBackendInterceptor } from "./interceptors/agent-fake-backend.interceptor";
import { OperativeDashboardFakeBackendInterceptor } from "./interceptors/operative-dashboard-fake-backend.interceptor";
import { CustomErrorHandlerInterceptor } from "./interceptors/custom-error-handler.interceptor";
const INITIAL_LANGUAGE = "es";
registerLocaleData(localeEs, "es");
registerLocaleData(localeEn, "en");
export function createTranslateLoader(http: HttpClient) {
return new MultiTranslateHttpLoader(http, [
{ prefix: "./assets/i18n/", suffix: ".json" },
{ prefix: "./assets/i18n/menu/", suffix: ".json" },
{ prefix: "./assets/i18n/layouts/", suffix: ".json" },
{ prefix: "./assets/i18n/gallery/", suffix: ".json" },
{ prefix: "./assets/i18n/security/", suffix: ".json" },
{ prefix: "./assets/i18n/settings/", suffix: ".json" },
]);
}
@NgModule({
declarations: [AppComponent, HomeComponent],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
CommonModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
ToastrModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient],
},
}),
XdfCommonsModule.forRoot({
language: INITIAL_LANGUAGE,
}),
XdfLayoutsModule,
XdfSecurityModule,
XdfGalleryModule,
MatDialogModule,
MatButtonModule,
AgentModule,
MatIconModule,
],
providers: [
{ provide: MAT_DATE_LOCALE, useValue: INITIAL_LANGUAGE },
{
provide: DateAdapter,
useClass: MomentDateAdapter,
deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS],
},
{ provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
{
provide: MatPaginatorIntl,
useClass: CustomMatPaginatorIntl,
deps: [TranslateService],
},
{ provide: DATERANGEPICKER_LOCALE, useValue: INITIAL_LANGUAGE },
{
provide: DaterangepickerLocaleService,
useClass: DaterangepickerLocaleService,
},
{ provide: LocationStrategy, useClass: HashLocationStrategy },
{ provide: NotificationService, useClass: ToastNotificationService },
{ provide: SettingsService, useClass: ByteSettingsService },
ConflictErrorDialogService,
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorHandleInterceptor,
multi: true,
},
// { provide: HTTP_INTERCEPTORS, useClass: ErrorsHandler, multi: true},
{ provide: ResourceAuthGuard, useClass: ResourceAuthGuard },
// descomentar estas lineas para OAUTH
//{ provide: AuthGuard, useClass: OAuthGuard },
//{ provide: AuthenticationService, useClass: OAuthAuthenticationService },
//{ provide: APP_INITIALIZER, useFactory: loginLoaderFactory, deps: [AuthenticationService], multi: true },
// Para probar mantenimientos
// comentar estas lineas para OAUTH
{ provide: AuthGuard, useClass: AuthGuard },
{ provide: AuthenticationService, useClass: ByteAuthenticationService },
{
provide: HTTP_INTERCEPTORS,
useClass: AuthenticationFakeBackendInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: SettingsFakeBackendInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: CustomProgramsFakeBackendInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: AgentFakeBackendInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: OperativeDashboardFakeBackendInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: init_app,
deps: [InitCommonsService, TranslateService],
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
export function init_app(appLoaderService: InitCommonsService) {
return () => appLoaderService.initializeApp();
}
// required for AOT compilation
// export function HttpLoaderFactory(http: HttpClient) {
// return new TranslateHttpLoader(http);
// }
// Para OAUTH
export function loginLoaderFactory(provider: OAuthAuthenticationService) {
return () => provider.login(null, null).toPromise();
}
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { SortField, DIRECTION } from '@xdf/commons';
const basePath = '/service/agent';
const tableData: Array<any> = [
{
id: 361,
name: 'BANCO DE CREDITO DEL PERU',
version: '0.0.1',
status: 'Activo',
country: 'Perú',
timezone: 'GMT-5',
avatar: 'https://www.w3schools.com/howto/img_avatar.png'
},
{
id: 362,
name: 'BANCO CONTINENTAL',
version: '0.0.5',
status: 'Activo',
country: 'Perú',
timezone: 'GMT-5',
avatar: 'https://lh3.googleusercontent.com/VHB9bVB8cTcnqwnu0nJqKYbiutRclnbGxTpwnayKB4vMxZj8pk1220Rg-6oQ68DwAkqO=s180-rw'
},
{
id: 363,
name: 'JUAN DE ARONA',
version: '0.0.1',
status: 'Activo',
country: 'Perú',
timezone: 'GMT-5',
avatar: 'https://www.w3schools.com/howto/img_avatar.png'
},
{
id: 364,
name: 'JUAN DE ARONA 2',
version: '0.0.1',
status: 'Activo',
country: 'Perú',
timezone: 'GMT-5',
avatar: 'https://www.w3schools.com/howto/img_avatar.png'
}
];
const data: Array<any> = [
{
id: 361,
name: 'BANCO DE CREDITO DEL PERU',
version: '0.0.1',
status: 'Activo',
country: 'Perú',
timezone: 'GMT-5',
avatar: 'https://www.w3schools.com/howto/img_avatar.png'
}
];
const countries: Array<any> = [
{
id: 1,
name: 'Perú',
timezones: [
'GMT-5'
]
},
{
id: 2,
name: 'Argentina',
timezones: [
'GMT-3'
]
},
{
id: 3,
name: 'Brazil',
timezones: [
'GMT-5',
'GMT-4',
'GMT-3',
'GMT-2'
]
}
];
const fakeAgentData = {
id: 361,
name: 'BANCO DE CREDITO DEL PERU',
description: 'El mejor banco que te roba la plata con intereses',
version: '0.0.1',
status: 'Activo',
countryId: 1,
timezone: 'GMT-5',
avatar: '',
frequentQuestions: [
{
id: 1,
filename: 'questions-opt.xlsx',
description: 'primer archivo',
status: 'PS',
user: '',
uploadDate: ''
},
{
id: 2,
filename: 'questions-training.xlsx',
description: 'segundo archivo',
status: 'PS',
user: '',
uploadDate: ''
},
{
id: 3,
filename: 'questions-brainstorming.xlsx',
description: 'tercero archivo',
status: 'PS',
user: '',
uploadDate: ''
},
{
id: 4,
filename: 'questions-onu.xlsx',
description: 'cuarto archivo',
status: 'PS',
user: '',
uploadDate: ''
},
{
id: 5,
filename: 'questions-byte.xlsx',
description: 'quinto archivo',
status: 'PS',
user: '',
uploadDate: ''
}
]
};
const fakeAgentData2 = {
id: 361,
name: 'BANCO DE CREDITO DEL PERU',
description: 'El mejor banco que te roba la plata con intereses',
version: '0.0.1',
status: 'Activo',
countryId: 1,
timezone: 'GMT-5',
avatar: '',
frequentQuestions: []
};
const channels = [
{
id: 101,
name: 'Facebook Messenger',
image: 'https://img.icons8.com/color/452/facebook-messenger.png',
parameters: [
{
name: 'webhook',
label: 'webhook',
maxlength: 200,
type: 'text',
required: true,
traductions: { es: 'URL de devolución de llamada', en: 'Webhook' }
},
{
name: 'verification-token',
label: 'verification-token',
maxlength: 80,
type: 'text',
required: true,
traductions: { es: 'Token de verificación', en: 'Verification token' }
},
{
name: 'access-token',
label: 'access-token',
maxlength: 80,
type: 'text',
required: true,
traductions: { es: 'Token de acceso', en: 'Access token' }
}
]
},
{
id: 103,
name: 'WhatsApp',
image: 'https://img.icons8.com/color/452/whatsapp.png',
parameters: [
{
name: 'account-identifier',
label: 'account-identifier',
maxlength: 80,
type: 'text',
required: true,
traductions: { es: 'Identificador de cuenta', en: 'Account identifier' }
},
{
name: 'authentication-token',
label: 'authentication-token',
maxlength: 80,
type: 'text',
required: true,
traductions: { es: 'Token de autenticación', en: 'Authentication token' }
},
{
name: 'twillio-number',
label: 'twillio-number',
type: 'number',
required: true,
traductions: { es: 'Número de Twillio', en: 'Twillio number' }
}
]
}
];
const fileOK = {
uuid: '6850b631-5cb1-4614-8f0e-e43441d7bd35',
id: 100,
fileName: 'vacaciones-preguntas.xls',
description: 'Preguntas Vacaciones',
status: 'PS',
user: '',
uploadDate: ''
};
@Injectable()
export class AgentFakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(10))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
// case url.endsWith(basePath + '/page') && method === 'POST':
// return pagination(body);
// case url.indexOf(basePath + '/connection/data') !== -1 && method === 'GET ':
// return ok({});
// case url.indexOf(basePath + '/countries') !== -1 && method === 'GET':
// return ok(countries);
// case url.indexOf(basePath + '/channels') !== -1 && method === 'GET':
// return ok(channels);
// case url.indexOf(basePath + '/file-upload') !== -1 && method === 'POST':
// return ok(fileOK);
// case url.indexOf(basePath + '/') !== -1 && method === 'GET':
// return ok(fakeAgentData);
default:
// pass through any requests not handled above
return next.handle(request);
}
}
function pagination(dataRecibida: any) {
dataRecibida.totalItems = tableData.length;
const pageNumber = dataRecibida.currentPage;
const pageSize = dataRecibida.itemsPerPage ? dataRecibida.itemsPerPage : 5;
dataRecibida.totalPages = Math.ceil(dataRecibida.totalItems / pageSize);
if (dataRecibida.sortFields.length > 0) {
const sortField: any = dataRecibida.sortFields[0];
if (sortField.direction === DIRECTION.asc) {
tableData.sort((a, b) => {
if (JSON.stringify(a[sortField.field]) > JSON.stringify(b[sortField.field])) {
return 1;
} else if (JSON.stringify(a[sortField.field]) < JSON.stringify(b[sortField.field])) {
return -1;
}
return 0;
});
} else {
tableData.sort((a, b) => {
if (JSON.stringify(a[sortField.field]) < JSON.stringify(b[sortField.field])) {
return 1;
} else if (JSON.stringify(a[sortField.field]) > JSON.stringify(b[sortField.field])) {
return -1;
}
return 0;
});
}
}
dataRecibida.data = tableData.slice(pageNumber * pageSize, (pageNumber + 1) * pageSize);
return ok(dataRecibida);
}
function ok(bodyContent?: any) {
return of(new HttpResponse({ status: 200, body: bodyContent }));
}
function error(message: string) {
return throwError({ error: { message } });
}
}
}
import {
HttpRequest,
HttpResponse,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, of, EMPTY } from 'rxjs';
import { Injectable } from '@angular/core';
import { catchError, tap, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { NotificationService, NotificationType, AuthenticationService } from '@xdf/commons';
import { Router } from '@angular/router';
import { ConflictErrorDialogService } from '@xdf/gallery';
@Injectable()
export class CustomErrorHandlerInterceptor implements HttpInterceptor {
constructor(
private router: Router,
private translate: TranslateService,
private authenticationService: AuthenticationService,
private notificationService: NotificationService,
private conflictErrorDialogService: ConflictErrorDialogService) {
}
intercept(
req: HttpRequest<any>, next: HttpHandler): Observable<any> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
switch (error.status) {
case 401:
const message = this.translate.instant('message.error.unauthorized');
this.notificationService.showMessage(message, this.translate.instant('title.error'), NotificationType.error);
break;
case 404:
const messageError = error.error ? error.error : error.message;
this.notificationService.showMessage(messageError, this.translate.instant('title.error'), NotificationType.error);
break;
case 409:
const messageDuplicate = this.translate.instant('message.error.duplicated');
this.notificationService.showMessage(messageDuplicate, this.translate.instant('title.error'), NotificationType.error);
break;
case 419: // validar cual es el código correcto
this.conflictErrorDialogService.loadComponent(
null,
'title.error.conflict',
'message.error.conflict',
error.error);
break;
default:
if (error.status === 0) {
this.authenticationService.login(null, null).subscribe(
data => {
window.location.href = './';
});
} else {
let messageDefault = '';
if (error.error) {
if (error.error.params) {
const params = [];
error.error.params.forEach(element => {
params.push(this.translate.instant(element));
});
messageDefault = this.translate.instant(error.error.message, params);
} else {
if (error.error.message) {
messageDefault = this.translate.instant(error.error.message);
} else {
messageDefault = this.translate.instant(error.error);
}
}
} else {
messageDefault = this.translate.instant(error.message);
}
this.notificationService.showMessage(messageDefault, this.translate.instant('title.error'), NotificationType.error);
}
}
return throwError(error);
})
);
}
}
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import * as source from '../../assets/fake-data/programs-data.json';
import * as controlsSource from '../../assets/fake-data/controls-data.json';
const basePath = '/service/security/programs';
const basePathCotnrols = '/service/security/controls';
@Injectable()
export class CustomProgramsFakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
const data = (source as any).default;
const dataControls = (controlsSource as any).default;
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(50))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
case url.match('.*' + basePath) && method === 'GET':
return getList();
case url.match('.*' + basePathCotnrols + '.*') && method === 'GET':
return getControls();
default:
// pass through any requests not handled above
return next.handle(request);
}
}
function getList() {
return ok(data);
}
function getControls() {
return ok(dataControls);
}
// helper functions
function ok(bodyContent?) {
return of(new HttpResponse({ status: 200, body: bodyContent }));
}
function error(message: string) {
return throwError({ error: { message } });
}
}
}
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import * as source from '../../assets/fake-data/operative-dashboard-data.json';
const basePath = '/test';
@Injectable()
export class OperativeDashboardFakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
const data = (source as any).default;
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(50))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
case url.match('.*' + basePath) && method === 'POST':
return getData();
default:
// pass through any requests not handled above
return next.handle(request);
}
}
function getData() {
return ok(data);
}
// helper functions
function ok(bodyContent?) {
return of(new HttpResponse({ status: 200, body: bodyContent }));
}
function error(message: string) {
return throwError({ error: { message } });
}
}
}
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule, Routes } from "@angular/router";
import { AeropuertoListComponent } from "./view/aeropuerto-list/aeropuerto-list.component";
import { AuthGuard, ResourceAuthGuard } from "@xdf/security";
const routes: Routes = [
{
path: "airport",
component: AeropuertoListComponent,
canActivate: [AuthGuard, ResourceAuthGuard],
data: {
program: "CONVERSATIONAL_AIRPORT",
// breadcrumb: ''
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AeropuertoRoutingModule {}
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { AeropuertoListComponent } from "./view/aeropuerto-list/aeropuerto-list.component";
import { AeropuertoRoutingModule } from "./aeropuerto-routing.module";
@NgModule({
declarations: [AeropuertoListComponent],
imports: [CommonModule, AeropuertoRoutingModule],
})
export class AeropuertoModule {}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AeropuertoListComponent } from './aeropuerto-list.component';
describe('AeropuertoListComponent', () => {
let component: AeropuertoListComponent;
let fixture: ComponentFixture<AeropuertoListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AeropuertoListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AeropuertoListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'byte-aeropuerto-list',
templateUrl: './aeropuerto-list.component.html',
styleUrls: ['./aeropuerto-list.component.scss']
})
export class AeropuertoListComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard, ResourceAuthGuard } from '@xdf/security';
import { DirtyGuard } from '@xdf/gallery';
import { AgentComponent } from './view/agent/agent.component';
import { AgentDetailComponent } from './view/agent-detail/agent-detail.component';
import { AgentDataForWizardResolver } from './resolver/agent-data-wizard.resolver';
import { AgentDetailResolver } from './resolver/agent-detail.resolver';
import { AgentListComponent } from './view/agent-list/agent-list.component';
import { CountryDataForWizardResolver } from './resolver/country-data-wizard.resolver';
const routes: Routes = [
{
path: 'agent', component: AgentListComponent, canActivate: [AuthGuard, ResourceAuthGuard],
data: {
program: 'CONVERSATIONAL_AGENT',
// breadcrumb: ''
}
},
{
path: 'agent/detail/new',
// component: AgentDetailComponent,
component: AgentComponent,
resolve: {
countryData : CountryDataForWizardResolver
},
canActivate: [AuthGuard, ResourceAuthGuard], canDeactivate: [DirtyGuard],
data:
{
mode: 'create',
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Agentes',
backButton: true
}
},
{
path: 'agent/detail/edit/:code',
// component: AgentDetailComponent,
component: AgentComponent,
resolve: {
agentDetail: AgentDetailResolver,
countryData : CountryDataForWizardResolver
},
canActivate: [AuthGuard, ResourceAuthGuard], canDeactivate: [DirtyGuard],
data:
{
mode: 'edit',
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Agentes',
backButton: true
}
}, {
path: 'agent/detail/view/:code',
// component: AgentDetailComponent,
component: AgentComponent,
resolve: {
agentDetail: AgentDetailResolver,
countryData : CountryDataForWizardResolver
},
canActivate: [AuthGuard, ResourceAuthGuard],
data:
{
mode: 'view',
program: 'CONVERSATIONAL_AGENT',
breadcrumb: 'Agentes',
backButton: true
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AgentRoutingModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import {NgxTributeModule} from 'ngx-tribute';
import { MaterialFileInputModule } from 'ngx-material-file-input';
import { XdfGalleryModule } from '@xdf/gallery';
import { MatProgressSpinnerModule, MatDividerModule, MatSidenavModule, MatListModule } from '@angular/material';
import { MatStepperModule, MatDialogModule, MatMenuModule, MatIconModule } from '@angular/material';
import { MatSlideToggleModule, MatCheckboxModule, MatRippleModule, MatNativeDateModule } from '@angular/material';
import { MatChipsModule, MatExpansionModule, MatTooltipModule, MatToolbarModule } from '@angular/material';
import { MatTabsModule, MatTableModule, MatSortModule, MatSelectModule, MatRadioModule } from '@angular/material';
import { MatPaginatorModule, MatInputModule, MatFormFieldModule, MatButtonToggleModule } from '@angular/material';
import { MatButtonModule, MatCardModule, MatAutocompleteModule, MatSnackBarModule } from '@angular/material';
import { MatProgressBarModule } from '@angular/material';
import { SlickCarouselModule } from 'ngx-slick-carousel';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AgentRoutingModule } from './agent-routing.module';
import { AceEditorModule } from 'ng2-ace-editor';
import { AgentComponent } from './view/agent/agent.component';
import { AgentDetailComponent } from './view/agent-detail/agent-detail.component';
import { DynamicTranslatePipe } from './pipe/dynamic-translate.pipe';
import { CaGeneralInformationComponent } from './view/components/ca-general-information/ca-general-information.component';
import { CaFrequentQuestionsComponent } from './view/components/ca-frequent-questions/ca-frequent-questions.component';
import { CaDeploymentChannelsComponent } from './view/components/ca-deployment-channels/ca-deployment-channels.component';
import { AgentListComponent } from './view/agent-list/agent-list.component';
import { CaDeploymentChannelsModalComponent } from './view/components/ca-deployment-channels-modal/ca-deployment-channels-modal.component';
import { AgentStatusPipe } from './pipe/agent-status.pipe';
import { CaFileUploadModalComponent } from './view/components/ca-file-upload-modal/ca-file-upload-modal.component';
@NgModule({
entryComponents: [
CaDeploymentChannelsModalComponent,
CaFileUploadModalComponent
],
declarations: [
AgentComponent,
AgentListComponent,
AgentDetailComponent,
DynamicTranslatePipe,
CaGeneralInformationComponent,
CaFrequentQuestionsComponent,
CaDeploymentChannelsComponent,
CaDeploymentChannelsModalComponent,
AgentStatusPipe,
CaFileUploadModalComponent
],
imports: [
FormsModule,
ReactiveFormsModule,
MatAutocompleteModule,
MatCardModule,
MatButtonModule,
MatButtonToggleModule,
MatFormFieldModule,
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatRadioModule,
MatSelectModule,
MatSortModule,
MatTabsModule,
MatTableModule,
MatToolbarModule,
MatTooltipModule,
MatExpansionModule,
MatChipsModule,
MatNativeDateModule,
MatRippleModule,
MatCheckboxModule,
MatSlideToggleModule,
MatIconModule,
MatMenuModule,
MatDialogModule,
MatStepperModule,
MatListModule,
MatSidenavModule,
MatDividerModule,
MatSnackBarModule,
NgbModule,
SlickCarouselModule,
TranslateModule,
XdfGalleryModule,
AgentRoutingModule,
CommonModule,
AceEditorModule,
NgxTributeModule,
MatProgressBarModule,
MaterialFileInputModule
],
exports: [
]
})
export class AgentModule { }
import { AgentStatusPipe } from './agent-status.pipe';
describe('AgentStatusPipe', () => {
it('create an instance', () => {
const pipe = new AgentStatusPipe();
expect(pipe).toBeTruthy();
});
});
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'agentStatus'
})
export class AgentStatusPipe implements PipeTransform {
transform(value: any, ...args: any[]): any {
if (value) {
switch (value) {
case 'CR':
return 'label.created';
case 'DP':
return 'label.deployed';
case 'PS':
return 'label.sync-pending';
}
}
return value;
}
}
import { DynamicTranslatePipe } from './dynamic-translate.pipe';
describe('DynamicTranslatePipe', () => {
it('create an instance', () => {
const pipe = new DynamicTranslatePipe();
expect(pipe).toBeTruthy();
});
});
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Pipe({
name: 'dynamicTranslate'
})
export class DynamicTranslatePipe implements PipeTransform {
constructor(private translateService: TranslateService) {
}
transform(value: any, ...args: any[]): any {
const lang = this.translateService.currentLang ? this.translateService.currentLang : this.translateService.defaultLang;
let traductions;
if (args[0].param) {
if (typeof args[0].param === 'string') {
traductions = JSON.parse(args[0].param);
} else {
traductions = args[0].param;
}
}
if (traductions && traductions[lang]) {
return traductions[lang];
} else {
return value;
}
}
}
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AgentService } from '../service/agent.service';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AgentDataForWizardResolver implements Resolve<any> {
constructor(private service: AgentService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
const code = route.paramMap.get('code');
return this.service.getDataForWizard(parseInt(code, 10));
}
}
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AgentService } from '../service/agent.service';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AgentDetailResolver implements Resolve<any> {
constructor(private service: AgentService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
const code = route.paramMap.get('code');
return this.service.getResult(code);
}
}
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AgentService } from '../service/agent.service';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CountryDataForWizardResolver implements Resolve<any> {
constructor(private service: AgentService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
return this.service.getCountryDataForWizard();
}
}
import { TestBed } from '@angular/core/testing';
import { AgentService } from './agent.service';
describe('AgentService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AgentService = TestBed.get(AgentService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { DynaDataService } from '@xdf/gallery';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class AgentService extends DynaDataService {
serviceURL = './service/agent';
constructor(private httpClient: HttpClient) {
super(httpClient);
}
getDataForWizard(agentId: number) {
let params = {};
if (agentId) {
params = {
id: agentId
};
}
return this.httpClient.get(this.serviceURL + '/connection/data', {
params
});
}
getCountryDataForWizard() {
return this.httpClient.get(this.serviceURL + '/countries');
}
upload(formData) {
return this.httpClient.post<any>(this.serviceURL + '/file-upload', formData, {
reportProgress: true,
observe: 'events'
});
}
getChannelDatForWizard() {
return this.httpClient.get(this.serviceURL + '/channels');
}
synchronize(id: number, user: string) {
return this.httpClient.get(this.serviceURL + '/synchronize/' + id, {
params: {
user
}
});
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AgentDetailComponent } from './agent-detail.component';
describe('AgentDetailComponent', () => {
let component: AgentDetailComponent;
let fixture: ComponentFixture<AgentDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AgentDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AgentDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'byte-agent-detail',
templateUrl: './agent-detail.component.html',
styleUrls: ['./agent-detail.component.scss']
})
export class AgentDetailComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
.toolbar-option {
margin-left: 10px;
margin-right: 10px;
}
$fontcolor: #676a6c;
.content {
width: 100%;
display: flex;
padding-right: 0px !important;
.grid-container,
.filter-panel {
.crud-table {
margin-bottom: 0px;
width: 100%;
}
.form {
padding: 10px 20px 10px 20px;
}
}
.filter-panel {
margin-bottom: 10px;
}
}
.grid-ibox-content {
width: 100%;
}
.btn-group {
.btn {
font-size: 10px;
}
}
.td-view {
cursor: auto !important;
}
.mat-row-auth:hover {
background-color: #f8f9fa;
cursor: pointer !important;
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container mat-spinner {
margin: -10px auto 0 auto;
}
td {
vertical-align: middle !important;
color: $fontcolor;
font-size: 12px;
}
th {
vertical-align: middle !important;
}
tr.mat-header-row {
height: 50px !important;
}
tr.mat-footer-row,
tr.mat-row {
height: 40px !important;
}
.table-toolbar {
right: 10px;
top: 10px;
button {
font-size: 12px;
}
}
.status-button-bar {
button {
font-size: 16px;
}
padding-top: 8px;
padding-left: 15px;
}
.mat-raised-button {
padding: 0px 10px;
}
.icon-centered-button span.mat-button-wrapper {
display: flex;
}
.icon-centered-button mat-icon {
font-size: 15px;
padding-top: 2px;
}
::ng-deep .mat-sort-header-arrow {
visibility: hidden;
}
::ng-deep .mat-sort-header-button {
.mat-icon {
padding-left: 10px;
font-size: 12px;
padding-top: 5px;
}
}
th {
background: rgb(242, 242, 242);
}
tr.example-detail-row {
height: 0 !important;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: #dedede;
cursor: pointer;
}
tr.example-element-row:not(.example-expanded-row):active {
background: #efefef;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail {
overflow: hidden;
display: flex;
}
.example-detail-row td {
padding-top: 0px;
padding-bottom: 0px;
border: none !important;
background-color: #f2f2f2;
}
i.state {
font-size: 1.3em;
}
i.fa-key.pull-left {
color: rgba(128, 128, 128, 0.33);
margin-right: 6px;
}
//////
table {
width: 100%;
}
tr.mat-header-row {
height: 25px;
}
tr.mat-footer-row,
tr.mat-row {
height: 40px;
}
.button-grid {
margin: 2px 5px;
mat-icon {
padding-top: 2px;
}
}
.btn-header-new {
margin-top: -4px;
}
.btn-grid-filter {
height: 36px;
width: 85px;
padding-left: 0px;
padding-top: 2px;
}
.mat-row:hover {
cursor: pointer;
background-color: #f8f9fa;
}
.spinner-container {
height: 100%;
width: 100%;
padding-top: 50px;
position: fixed;
}
.spinner-container mat-spinner {
margin: -10px auto 0 auto;
}
.mat-raised-button {
padding: 0px 10px;
}
.icon-centered-button span.mat-button-wrapper {
display: flex;
}
.icon-centered-button mat-icon {
font-size: 15px;
padding-top: 2px;
}
.ibox-content {
padding-right: 0px;
}
.content {
width: 100%;
display: flex;
padding-right: 0px !important;
}
.grid-ibox-content {
width: 100%;
}
.avatar {
vertical-align: middle;
width: 35px;
height: 35px;
border-radius: 50%;
}
::ng-deep .mat-menu-panel {
overflow: hidden !important;
}
.th-avatar {
width: 130px;
}
::ng-deep .text-right > .mat-sort-header-container {
justify-content: flex-end !important;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AgentListComponent } from './agent-list.component';
describe('AgentComponent', () => {
let component: AgentListComponent;
let fixture: ComponentFixture<AgentListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AgentListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AgentListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, ViewChild, ViewContainerRef, Input, AfterViewInit } from '@angular/core';
import { MatPaginator, MatSort } from '@angular/material';
import { ColumnTemplate, DynaDataSource, ConfirmationDialogService, extractRSQL, FieldFilter } from '@xdf/gallery';
import { Pagination, SortField, NotificationType, NotificationService, AuthenticationService } from '@xdf/commons';
import { Router, ActivatedRoute } from '@angular/router';
import { first, tap } from 'rxjs/operators';
import { TagFilter } from '@xdf/gallery/lib/views/gallery/ngx-tags-input/model/tag-filter';
import { TranslateService } from '@ngx-translate/core';
import { AgentService } from '../../service/agent.service';
const columnTemplateArray = [
{
name: 'avatar',
sortable: false,
title: 'agent_avatar',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'id',
sortable: true,
title: 'agent_code',
visible: true,
filtable: true,
style: { width: '75px' },
type: 'number',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'name',
sortable: true,
title: 'agent_name',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'version',
sortable: true,
title: 'agent_publish_version',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'status',
sortable: true,
title: 'agent_status',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'countryName',
sortable: true,
title: 'agent_country',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
},
{
name: 'timezone',
sortable: true,
title: 'agent_timezone',
visible: true,
filtable: true,
style: { width: 'auto' },
type: 'string',
aliasName: null,
queryName: null,
responsive: null,
styles: null,
values: null,
digitsInfo: null,
dateFormat: null,
colStyle: null
}
];
const fieldFilters = [
new FieldFilter('agent_code', 'id', 'id', 'number', undefined),
new FieldFilter('agent_name', 'name', 'name', 'string', undefined),
new FieldFilter('agent_status', 'status', 'status', 'valpos', (
{ DP: 'label.deployed', PS: 'label.sync-pending' }
) as any),
];
@Component({
selector: 'byte-agent-list',
templateUrl: './agent-list.component.html',
styleUrls: ['./agent-list.component.scss']
})
export class AgentListComponent implements OnInit, AfterViewInit {
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
@ViewChild(MatSort, { static: false }) sort: MatSort;
sortColumn: string;
sortDirection: string;
statusQuickFilter: string;
pagingSize = 10;
programIdentifier = 'agent';
columnTemplateArray: ColumnTemplate[];
dataSource: DynaDataSource;
displayedColumns = new Array<string>();
fieldFilters: Array<FieldFilter> = fieldFilters;
filterTags = [];
pagination: Pagination;
public resourceAuth: any;
constructor(
private router: Router,
private route: ActivatedRoute,
private agentService: AgentService,
private notificationService: NotificationService,
private translateService: TranslateService,
protected confirmationDialogService: ConfirmationDialogService,
private authenticationService: AuthenticationService,
protected vcRef: ViewContainerRef
) { }
ngAfterViewInit(): void {
this.sort.sortChange.subscribe(() => {
if (!this.sort.disabled && this.sort.active) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection = this.sort.direction;
sortField.field = this.sortColumn = this.sort.active;
this.pagination.sortFields.push(sortField);
}
this.paginator.pageIndex = 0;
this.dataSource.load(this.pagination);
});
this.paginator.page
.pipe(
tap(() => {
this.pagination.currentPage = this.paginator.pageIndex;
this.pagination.itemsPerPage = this.paginator.pageSize;
this.dataSource.load(this.pagination);
})
)
.subscribe();
}
ngOnInit() {
this.sortColumn = 'id';
this.sortDirection = 'asc';
const authList = this.route.snapshot.paramMap['authorization'];
if (authList) {
this.resourceAuth = new Object();
authList.forEach(option => {
this.resourceAuth[option] = true;
});
}
this.columnTemplateArray = columnTemplateArray;
this.dataSource = new DynaDataSource(this.agentService);
const previousState = JSON.parse(localStorage.getItem(this.programIdentifier + '-state'));
if (previousState) {
const PAG = 'pagination';
const FILTERS = 'filtersTags';
this.pagination = previousState[PAG];
this.filterTags = previousState[FILTERS];
localStorage.removeItem(this.programIdentifier + '-state');
} else {
// Paginación
this.pagination = new Pagination();
this.pagination.currentPage = 0;
this.pagination.itemsPerPage = this.pagingSize;
this.pagination.filterExpression = 'id != 0';
// Ordenación por defecto
if (this.sortColumn && this.sortDirection) {
this.pagination.sortFields = new Array();
const sortField: SortField = new SortField();
sortField.direction = this.sortDirection;
sortField.field = this.sortColumn;
this.pagination.sortFields.push(sortField);
}
}
// calculando la columnas a pintar
this.columnTemplateArray.forEach(column => {
this.displayedColumns.push(column.name);
});
this.displayedColumns.push('actions');
this.dataSource.load(this.pagination);
}
onFilter(tags: Array<TagFilter>) {
this.filterTags = tags;
this.pagination.currentPage = 0;
// let tagsTemp = tags.slice();
const tagsTemp = tags.map(a => Object.assign({}, a));
for (let i = 0; i < tagsTemp.length; i++) {
if (tagsTemp[i].column !== 'id') {
tagsTemp[i].value = tags[i].value;
}
}
this.pagination.filterExpression = extractRSQL(tagsTemp);
if (this.pagination.filterExpression === null || this.pagination.filterExpression === undefined) {
this.pagination.filterExpression = 'id != 0';
}
this.dataSource.load(this.pagination);
}
onRefresh() {
this.dataSource.load(this.pagination);
}
delete(item) {
this.confirmationDialogService.loadComponent(
this.vcRef,
'title.delete.confirmation',
'message.delete.confirmation').subscribe(result => {
if (result) {
this.agentService.delete(item.id).subscribe((rslt: any) => {
if (rslt.statusCode !== 200) {
this.notificationService.showMessage(
this.translateService.instant(rslt.message), this.translateService.instant('label.error.message.title'),
NotificationType.error);
} else {
this.dataSource.load(this.pagination);
}
}
);
}
});
}
edit(item) {
this.router.navigate(['/configuration/agent/detail/edit/' + item.id]);
}
synchronize(item) {
const user = (this.authenticationService.currentUserValue ? this.authenticationService.currentUserValue.username : 'admin');
this.agentService.synchronize(item.id, user).pipe(first()).subscribe(() => {
this.notificationService.showMessage(this.translateService.instant('agent.synchronize.success'), undefined, NotificationType.success);
this.onRefresh();
});
}
view(item) {
this.router.navigate(['/configuration/agent/detail/view/' + item.id]);
}
create() {
this.router.navigate(['/configuration/agent/detail/new']);
}
}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<!-- <div class="ibox-title">
<h5>{{'reconciliation.title' | translate}}</h5>
</div> -->
<div class="ibox-content">
<mat-horizontal-stepper labelPosition="bottom" #stepper [linear]="true">
<mat-step state="info">
<ng-template matStepLabel>
<span class="font-bold">{{'label.general-information.title' | translate}}</span>
<div class="font-size-description" [title]="'label.general-information.description' | translate">
{{'label.general-information.description' | translate}}
</div>
</ng-template>
<byte-ca-general-information [stepper]="stepper" (nextPage)="updateDirtyStatus($event)"></byte-ca-general-information>
</mat-step>
<mat-step state="questions">
<ng-template matStepLabel>
<span class="font-bold">{{'label.questions.title' | translate}}</span>
<div class="font-size-description" [title]="'label.questions.description' | translate">
{{'label.questions.description' | translate}}
</div>
</ng-template>
<byte-ca-frequent-questions [stepper]="stepper" (nextPage)="updateDirtyStatus($event)"></byte-ca-frequent-questions>
</mat-step>
<mat-step state="deployment-channels">
<ng-template matStepLabel>
<span class="font-bold">{{'label.deployment-channels.title' | translate}}</span>
<div class="font-size-description" [title]="'label.deployment-channels.description' | translate">
{{'label.deployment-channels.description' | translate}}
</div>
</ng-template>
<byte-ca-deployment-channels [stepper]="stepper" [changes]="generalDirty"></byte-ca-deployment-channels>
</mat-step>
<!--
<mat-step state="summary">
<ng-template matStepLabel>
<span class="font-bold">{{'label.summary.title' | translate}}</span>
<div class="font-size-description" [title]="'label.summary.description' | translate">
{{'label.summary.description' | translate}}
</div>
</ng-template>
<byte-es-summary [stepper]="stepper"></byte-es-summary>
</mat-step>
-->
<ng-template matStepperIcon="info">
<mat-icon>info</mat-icon>
</ng-template>
<ng-template matStepperIcon="questions">
<mat-icon>dns</mat-icon>
</ng-template>
<ng-template matStepperIcon="deployment-channels">
<mat-icon>chat</mat-icon>
</ng-template>
<!--
<ng-template matStepperIcon="summary">
<mat-icon>done_all</mat-icon>
</ng-template>
-->
</mat-horizontal-stepper>
</div>
</div>
</div>
</div>
</div>
::ng-deep .info-block {
text-align: center;
vertical-align: middle;
line-height: 150px;
min-height: 300px;
}
::ng-deep .footer-bar {
margin: 0 10px 10px;
}
::ng-deep .border-all {
border: solid 1px #d0c9c9ab;
}
::ng-deep .panel-viewer-wrapper {
border: 1px solid rgba(0, 0, 0, 0.03);
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
margin: 5px 5px 10px;
}
.font-size-description {
white-space: normal;
font-size: 11px;
}
::ng-deep .mat-step-header[aria-selected="true"] {
/*background: #c3ccff !important;*/
background: #c3ccff9e !important;
border: solid 1px #c3ccff80;
border-radius: 9px !important;
}
::ng-deep .mat-horizontal-stepper-header {
pointer-events: none !important;
}
::ng-deep .mat-card {
border-left: solid 0px #3f51b5;
transition: border-left 0.125s ease-in !important;
&.active {
border-left: solid 5px #3f51b5;
}
}
::ng-deep .panel-viewer-title > .mat-icon {
height: 0px;
width: 15px;
vertical-align: sub;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AgentComponent } from './agent.component';
describe('AgentComponent', () => {
let component: AgentComponent;
let fixture: ComponentFixture<AgentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AgentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AgentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, ViewChild, Input, AfterContentChecked, AfterViewInit } from '@angular/core';
import { ChangeDetectionStrategy, AfterContentInit, ViewContainerRef } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { CaGeneralInformationComponent } from '../components/ca-general-information/ca-general-information.component';
import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
import { ActivatedRoute, Router } from '@angular/router';
import { NavigationService } from '@xdf/layouts';
import { IDirty } from '@xdf/gallery';
import { CaFrequentQuestionsComponent } from '../components/ca-frequent-questions/ca-frequent-questions.component';
import { MatStepper } from '@angular/material';
import { CaDeploymentChannelsComponent } from '../components/ca-deployment-channels/ca-deployment-channels.component';
@Component({
selector: 'byte-agent',
templateUrl: './agent.component.html',
styleUrls: ['./agent.component.scss'],
providers: [{
provide: STEPPER_GLOBAL_OPTIONS, useValue: { displayDefaultIndicatorType: false }
}]
})
export class AgentComponent implements OnInit, IDirty {
@ViewChild(CaGeneralInformationComponent, { static: true })
generalInformationComponent: CaGeneralInformationComponent;
@ViewChild(CaFrequentQuestionsComponent, { static: true })
frequentQuestionsComponent: CaFrequentQuestionsComponent;
@ViewChild(CaDeploymentChannelsComponent, { static: true })
deploymentChannelsComponent: CaDeploymentChannelsComponent;
@ViewChild('stepper', { static: true }) stepper: MatStepper;
agentDetail: any;
isLinear = true;
isNew = true;
resourceAuth: any;
// dirty acumulado
generalDirty = false;
constructor(
private vcRef: ViewContainerRef,
private activatedRoute: ActivatedRoute,
private backService: NavigationService,
private router: Router
) {
this.backService.backAnnounced$.subscribe(data => {
this.router.navigate(['/configuration/agent']);
});
}
isDirty(): boolean {
let dirty = false;
dirty = this.generalInformationComponent.isDirty()
|| this.frequentQuestionsComponent.isDirty();
if (dirty) {
dirty = this.deploymentChannelsComponent.isDirty();
}
return dirty;
}
getRef(): ViewContainerRef {
return this.vcRef;
}
ngOnInit() {
this.stepper.selectedIndex = 0;
const authList = this.activatedRoute.snapshot.paramMap['authorization'];
this.resourceAuth = new Object();
if (authList) {
authList.forEach(option => {
this.resourceAuth[option] = true;
});
}
console.log(this.resourceAuth);
this.isNew = this.activatedRoute.snapshot.data.mode === 'create';
if (this.isNew) {
this.agentDetail = {
};
} else {
this.agentDetail = this.activatedRoute.snapshot.data.agentDetail;
}
this.generalInformationComponent.buildForm(this.agentDetail);
this.frequentQuestionsComponent.buildForm(this.agentDetail);
this.deploymentChannelsComponent.buildForm(this.agentDetail);
}
updateDirtyStatus(event: boolean) {
this.generalDirty = this.generalDirty || event;
}
}
<div class="title_section divider">
<div class="hidden-xs" *ngIf="applicationSettings?.logoBase64">
<img class="logo_header_popup"
src="data:image/{{applicationSettings.tipo}};base64,{{applicationSettings.logoBase64}}" />
</div>
<div class="title_header_popup">
<h2 mat-dialog-title>
<span> {{'label.deployment-channels.configuration' | translate}} </span>
</h2>
</div>
</div>
<mat-dialog-content>
<h4>
{{'label.deployment-channels.configuration.description' | translate}}
<!-- -->
</h4>
<div>
<ng-container *ngFor="let channel of channels">
<mat-card class="channel-card" (click)="selectChannel(channel)"
[ngClass]="{ 'active': selectedChannel === channel}" *ngIf="!channel.disabled">
<mat-card-header>
<img [src]="channel.image" mat-card-avatar>
<mat-card-title>{{channel.name}}</mat-card-title>
</mat-card-header>
</mat-card>
</ng-container>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>{{'btn.cancel' | translate}}</button>
<button mat-button (click)="ok()" cdkFocusInitial
[disabled]="!selectedChannel">{{'btn.accept' | translate}}</button>
</mat-dialog-actions>
\ No newline at end of file
.icon {
vertical-align: middle;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 10px;
}
::ng-deep .mat-card-header .mat-card-title {
margin-bottom: 12px;
margin-top: 10px;
}
.channel-card {
cursor: pointer;
margin-bottom: 8px;
transition: border-left 0.125s ease-in;
border-left: solid 0px #3f51b5;
&.active {
border-left: solid 5px #3F51B5 !important;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CaDeploymentChannelsModalComponent } from './ca-deployment-channels-modal.component';
describe('CaDeploymentChannelsModalComponent', () => {
let component: CaDeploymentChannelsModalComponent;
let fixture: ComponentFixture<CaDeploymentChannelsModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CaDeploymentChannelsModalComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CaDeploymentChannelsModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
@Component({
selector: 'byte-ca-deployment-channels-modal',
templateUrl: './ca-deployment-channels-modal.component.html',
styleUrls: ['./ca-deployment-channels-modal.component.scss']
})
export class CaDeploymentChannelsModalComponent implements OnInit {
channels: Array<any>;
selectedChannel: any;
applicationSettings: any;
constructor(
public dialogRef: MatDialogRef<CaDeploymentChannelsModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.channels = data.channels;
}
ngOnInit() {
}
ok() {
this.dialogRef.close(this.selectedChannel);
}
selectChannel(channel: any) {
this.selectedChannel = channel;
}
}
<div class="panel-viewer-wrapper">
<div class="panel-viewer-title">
<mat-icon>chat</mat-icon>
{{'label.deployment-channels.title' | translate}}
</div>
<div class="panel-viewer-body">
<!-- <div class="row pb-2 text-center">
<div class="col-12">
<h1>{{'label.deployment-channels' | translate}}</h1>
</div>
</div> -->
<div class="row pb-2 justify-content-center">
<div class="col-md-6 col-sm-12">
<!-- <div class="alert alert-warning" *ngIf="!viewMode">
<i class="fa fa-warning"></i>&nbsp;&nbsp;<span
[innerHTML]="'label.summary.warning' | translate"></span>
</div> -->
<h4 class="mt-3">
<!-- <button class="pull-right" mat-icon-button (click)="addChannel()" *ngIf="!viewMode && isAvaliableChannels">
<i class="fa fa-plus"></i>
</button> -->
<button type="button" class="btn btn-outline-primary btn-sm pull-right" (click)="addChannel()"
*ngIf="!viewMode && (deploymentChannels && deploymentChannels.length) && isAvaliableChannels">
<i class="fa fa-file"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'Añadir canal' | translate}}
</span>
</button>
<b>{{'label.channels' | translate}}</b>
</h4>
<mat-accordion *ngIf="!isLoadingDataChannel">
<form [formGroup]="formGroup" novalidate autocomplete="off">
<mat-expansion-panel *ngFor="let channel of deploymentChannels; let i = index"
[expanded]="step === i" (opened)="setStep(i)">
<mat-expansion-panel-header class="right-aligned-header" [collapsedHeight]="'60px'">
<mat-panel-title>
<img [src]="channel.channelImage" class="icon">
<div>
<span style="margin-right: 8px;">{{ channel.channelName }}</span>
<ng-container *ngIf="channel?.suggestTitle && channel?.suggestDetail">
<ng-template #popContent><div [innerHTML]="channel?.suggestDetail"></div></ng-template>
<ng-template #popoverTitle><div [innerHTML]="channel?.suggestTitle"></div></ng-template>
<i class="fa fa-info-circle" placement="top" style="color: rgba(0, 0, 0, 0.54);"
[ngbPopover]="popContent"
[popoverTitle]="popoverTitle" triggers="hover"></i>
</ng-container>
<!-- <small class="text-muted">{{ channel.name }}</small> -->
</div>
</mat-panel-title>
<mat-panel-description>
<!-- <label class="label label-success"
*ngIf="channel.active">{{'label.active' | translate}}</label>
<label class="label label-default"
*ngIf="!channel.active">{{'label.inactive' | translate}}</label> -->
<mat-slide-toggle [(ngModel)]="channel.active" [disabled]="viewMode"
style="margin-top: 0;" [ngModelOptions]="{standalone: true}" color="primary"
(click)="$event.stopPropagation();">
<span *ngIf="channel.active">{{'label.active' | translate}}</span>
<span *ngIf="!channel.active">{{'label.inactive' | translate}}</span>
</mat-slide-toggle>
</mat-panel-description>
</mat-expansion-panel-header>
<div formGroupName="{{channel.channelName}}">
<div class="row">
<!-- <div class="col-6">
<mat-form-field class="amd-form-control">
<mat-label>{{'label.name' | translate}}</mat-label>
<input matInput maxlength="80" [type]="text" required="true"
[disabled]="viewMode" [(ngModel)]="channel.name"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
</div> -->
<!-- <div class="col-6">
<mat-slide-toggle [(ngModel)]="channel.active" [disabled]="viewMode"
[ngModelOptions]="{standalone: true}" color="primary">
<span *ngIf="channel.active">{{'label.active' | translate}}</span>
<span *ngIf="!channel.active">{{'label.inactive' | translate}}</span>
</mat-slide-toggle>
</div> -->
</div>
<div class="row" *ngFor="let field of channel.parameters">
<div class="col-12">
<mat-form-field class="amd-form-control">
<mat-label>{{field.label | dynamicTranslate: {param: field.traductions } }}:
</mat-label>
<!--
[pattern]="field.regex"
-->
<input matInput [maxlength]="field.maxlength"
[disabled]="viewMode"
[type]="(field.type ? field.type : 'text')"
[required]="field.required"
[pattern]="field?.regex" [formControlName]="field.channelParamName">
<!-- <mat-error *ngIf="fieldForm.errors?.pattern">
{{ 'Formato inválido' | translate }}</mat-error> -->
<mat-error *ngFor="let error of getErrors(channel.channelName, field.channelParamName, field.label)">{{ 'message.error.' + error.name | translate : error.prop}}
</mat-error>
</mat-form-field>
</div>
</div>
</div>
<mat-action-row *ngIf="!viewMode">
<button mat-button color="warn" (click)="deleteChannel(i)">
<i
class="fa fa-trash"></i><span>&nbsp;&nbsp;{{'label.delete-channel' | translate}}</span>
</button>
</mat-action-row>
</mat-expansion-panel>
</form>
</mat-accordion>
<div *ngIf="!(deploymentChannels && deploymentChannels.length)" class="border-all text-center">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addChannel()"
*ngIf="!viewMode && isAvaliableChannels">
<i class="fa fa-file"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'Añadir canal' | translate}}
</span>
</button>
<!-- {{'label.delete-channel' | translate}} -->
</div>
</div>
</div>
</div>
<div class="footer-bar">
<div class="row">
<div class="col-12">
<button mat-stroked-button matStepperPrevious class="pull-left">
<i class="fa fa-chevron-left"></i>
<span class="pl-1">{{'btn.previous' | translate}}</span>
</button>
<button mat-stroked-button color="primary" type="submit" (click)="save()" class="pull-right"
*ngIf="!viewMode">
<span class="pr-1">{{'btn.save' | translate}}</span>
<i class="fa fa-chevron-right"></i>
</button>
<button mat-stroked-button (click)="exit()" class="pull-right" *ngIf="viewMode">
<span class="pr-1">{{'application.options.exit' | translate}}</span>
<i class="fa fa-logout"></i>
</button>
</div>
</div>
</div>
</div>
\ No newline at end of file
.icon {
vertical-align: middle;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 10px;
}
::ng-deep .right-aligned-header > .mat-content {
justify-content: space-between;
}
::ng-deep .mat-content > mat-panel-title {
flex: 0 0 auto;
}
::ng-deep .mat-content > mat-panel-description {
flex: 0 0 auto;
}
::ng-deep .mat-expansion-indicator {
margin-bottom: 10px;
}
::ng-deep mat-slide-toggle {
margin-top: 14px;
}
.border-all.text-center {
padding: 50px;
border-radius: 4px;
}
.label.label-success {
background-color: #3f51b5;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CaDeploymentChannelsComponent } from './ca-deployment-channels.component';
describe('CaDeploymentChannelsComponent', () => {
let component: CaDeploymentChannelsComponent;
let fixture: ComponentFixture<CaDeploymentChannelsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CaDeploymentChannelsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CaDeploymentChannelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { MatStepper, MatDialog, ErrorStateMatcher, MatInput } from '@angular/material';
import { CaDeploymentChannelsModalComponent } from '../ca-deployment-channels-modal/ca-deployment-channels-modal.component';
import { AgentService } from '../../../service/agent.service';
import { first } from 'rxjs/operators';
import { NotificationType, NotificationService } from '@xdf/commons';
import { TranslateService } from '@ngx-translate/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Form, FormArray, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { ValidatorUtils } from '@xdf/gallery';
@Component({
selector: 'byte-ca-deployment-channels',
templateUrl: './ca-deployment-channels.component.html',
styleUrls: ['./ca-deployment-channels.component.scss']
})
export class CaDeploymentChannelsComponent implements OnInit {
@Input()
stepper: MatStepper;
@Input()
changes = false;
@ViewChild('fieldForm', {static: false})
fieldFormList: any;
agentDetail: any;
channels: Array<any>;
deploymentChannels: Array<any> = new Array<any>();
dirty = true;
step = -1;
viewMode = false;
isAvaliableChannels = false;
mode = 'new';
detailText = 'Detail';
titleText = 'Titulo';
isLoadingDataChannel = true;
formGroup: FormGroup;
constructor(
private activatedRoute: ActivatedRoute,
private agentService: AgentService,
private matDialog: MatDialog,
private translateService: TranslateService,
private notificationService: NotificationService,
private router: Router,
private validatorUtils: ValidatorUtils
) {
this.formGroup = new FormGroup({});
this.mode = this.activatedRoute.snapshot.data.mode;
this.viewMode = this.mode === 'view';
}
ngOnInit() {
this.agentService.getChannelDatForWizard().pipe(first()).subscribe((result: any) => {
if (result) {
this.channels = result;
} else {
this.channels = [];
}
this.buildList();
this.buildAvaliableChannels();
this.isLoadingDataChannel = false;
}, error => {
this.isLoadingDataChannel = false;
});
}
buildForm(agentDetail: any) {
this.agentDetail = agentDetail;
this.deploymentChannels = (agentDetail.deploymentChannels || []);
this.buildList();
}
buildList() {
if (this.channels && this.deploymentChannels) {
for (const deploymentChannel of this.deploymentChannels) {
for (const channel of this.channels) {
if (channel.id === deploymentChannel.channelId) {
deploymentChannel.channelName = channel.name;
deploymentChannel.channelImage = channel.image;
deploymentChannel.active = (deploymentChannel.status === 'AC');
deploymentChannel.suggestTitle = channel.suggestTitle;
deploymentChannel.suggestDetail = channel.suggestDetail;
this.formGroup.addControl(channel.name, new FormGroup({}));
for (const parameter of deploymentChannel.parameters) {
for (const field of channel.parameters) {
if (parameter.channelParamName === field.name) {
const formArray = this.formGroup.controls[channel.name] as FormGroup;
parameter.label = field.label;
parameter.type = field.type;
parameter.required = field.required;
parameter.traductions = field.traductions;
parameter.maxlength = field.maxlength;
parameter.regex = field.regex;
formArray.addControl(field.name, new FormControl('', field.required ? [Validators.required] : []));
break;
}
}
}
break;
}
}
}
}
console.log(this.formGroup);
}
buildAvaliableChannels() {
this.isAvaliableChannels = false;
mainLoop: for (const channel of this.channels) {
channel.disabled = false;
for (const deploymentChannel of this.deploymentChannels) {
if (channel.id === deploymentChannel.channelId) {
channel.disabled = true;
continue mainLoop;
}
}
if (!channel.disabled) {
this.isAvaliableChannels = true;
}
}
}
addChannel() {
const dialog = this.matDialog.open(CaDeploymentChannelsModalComponent, {
width: '500px',
data: {
message: '',
channels: this.channels
}
});
dialog.afterClosed().subscribe(result => {
if (result) {
const selectedChannel = { ...result };
selectedChannel.channelId = selectedChannel.id;
selectedChannel.channelName = selectedChannel.name;
selectedChannel.channelImage = selectedChannel.image;
selectedChannel.id = undefined;
selectedChannel.name = undefined;
this.deploymentChannels.push(selectedChannel);
this.setStep(this.deploymentChannels.length - 1);
this.buildAvaliableChannels();
}
});
}
deleteChannel(i) {
this.deploymentChannels.splice(i, 1);
this.buildAvaliableChannels();
}
exit() {
this.dirty = false;
this.router.navigate(['/configuration/agent']);
}
isDirty() {
return this.dirty;
}
save() {
const isValid: boolean = this.validate();
if (!isValid) {
if (this.fieldFormList && this.fieldFormList.control) {
this.fieldFormList.control.markAsTouched();
}
return;
}
const agent: any = {};
agent.id = this.agentDetail.id;
agent.name = this.agentDetail.name;
agent.description = this.agentDetail.description;
agent.version = this.agentDetail.version;
agent.countryId = this.agentDetail.countryId;
agent.timezone = this.agentDetail.timezone;
agent.avatar = this.agentDetail.avatar;
agent.language = this.agentDetail.language;
agent.type = this.agentDetail.type;
agent.status = this.agentDetail.status;
agent.deploymentChannels = [];
for (const deploymentChannel of this.deploymentChannels) {
const deploymentChannelTmp: any = {};
deploymentChannelTmp.id = deploymentChannel.id;
deploymentChannelTmp.name = deploymentChannel.name;
deploymentChannelTmp.status = deploymentChannel.active ? 'AC' : 'IN';
deploymentChannelTmp.channelId = deploymentChannel.channelId;
deploymentChannelTmp.parameters = [];
for (const parameter of deploymentChannel.parameters) {
const parameterTmp: any = {};
parameterTmp.id = parameter.id;
parameterTmp.value = parameter.value;
parameterTmp.channelParamName = parameter.channelParamName ? parameter.channelParamName : parameter.name;
deploymentChannelTmp.parameters.push(parameterTmp);
}
agent.deploymentChannels.push(deploymentChannelTmp);
}
agent.frequentQuestions = this.agentDetail.frequentQuestions;
this.agentService.update(agent.id, agent).pipe(first()).subscribe(() => {
if (agent.id) {
this.dirty = false;
this.notificationService.showMessage(this.translateService.instant('agent.edit.success'), null, NotificationType.success);
this.router.navigate(['/configuration/agent']);
} else {
this.dirty = false;
this.notificationService.showMessage(this.translateService.instant('agent.create.success'), null, NotificationType.success);
this.router.navigate(['/configuration/agent']);
}
});
}
onChange(fieldForm) {
if (!fieldForm.control.touched) {
fieldForm.control.markAsTouched();
}
}
setStep(step) {
this.step = step;
}
validate(): boolean {
let valid = true;
mainLoop: for (const deploymentChannel of this.deploymentChannels) {
const deploymentChannelTmp: any = {};
// if (!deploymentChannel.name) {
// valid = false;
// break;
// }
for (const parameter of deploymentChannel.parameters) {
parameter.error = false;
if (!parameter.value) {
valid = false;
break mainLoop;
}
if (parameter.regex) {
const regex = new RegExp(parameter.regex);
if (!regex.test(parameter.value)) {
parameter.error = true;
valid = false;
break mainLoop;
}
}
}
}
if (!valid) {
this.notificationService.showMessage(this.translateService.instant('error.message.complete-form'), undefined, NotificationType.error);
}
return valid;
}
getErrors(channelName: string, name: string, placeholder: string) {
const channelFormGroup = (this.formGroup.controls[channelName] as FormGroup);
return channelFormGroup ? this.validatorUtils.getErrors(channelFormGroup.controls[name] as FormControl, name, placeholder) : [];
}
}
<div class="title_section divider">
<div class="hidden-xs" *ngIf="applicationSettings?.logoBase64">
<img class="logo_header_popup"
src="data:image/{{applicationSettings.tipo}};base64,{{applicationSettings.logoBase64}}" />
</div>
<div class="title_header_popup">
<h2 mat-dialog-title>
<i class="fa fa-exclamation-triangle" [hidden]="!fileErrorDetail"></i><span> {{'label.file-upload.title' | translate}} </span>
</h2>
</div>
</div>
<mat-dialog-content class="none-overflow" [hidden]="fileErrorDetail">
<h4>
{{'label.file-upload.description' | translate}}
</h4>
<form [formGroup]="formGroup" autocomplete="off">
<div class="row pt-2">
<div class="col-12">
<mat-form-field class="amd-form-control">
<input matInput [placeholder]="'label.description' | translate" formControlName="description"
maxlength="50" required>
</mat-form-field>
</div>
</div>
<div class="row pt-2" *ngIf="createMode">
<div class="col-12">
<mat-form-field class="amd-form-control">
<ngx-mat-file-input formControlName="file" [placeholder]="'label.file' | translate"
[accept]="'.xls,.xlsx'" required (change)="uploadFile($event.target.files)">
</ngx-mat-file-input>
<mat-icon matSuffix>folder</mat-icon>
</mat-form-field>
</div>
</div>
<div class="row pt-2" *ngIf="fileErrorDetail">
<div class="col-12">
<h4>
<b>Detalle de error</b>
</h4>
</div>
<div class="col-12">
<div class="example-container mat-elevation-z8">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef> {{ 'header.' + column | translate}} </th>
<td mat-cell *matCellDef="let element"> {{element[column] | translate}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>
</div>
<div class="row pt-2" *ngIf="!createMode">
<div class="col-12">
<mat-form-field class="amd-form-control">
<input matInput [placeholder]="'label.file' | translate" formControlName="filename" required>
<mat-icon matSuffix [matTooltip]="'label.file.tooltip' | translate" matTooltipPosition="above">
help_icon
</mat-icon>
</mat-form-field>
</div>
</div>
</form>
</mat-dialog-content>
<mat-dialog-content class="none-overflow" [hidden]="!fileErrorDetail">
<h4>
{{'label.file-upload.error.description' | translate}}
</h4>
<div class="row pt-2" *ngIf="fileErrorDetail">
<!-- <div class="col-12">
<h4>
<b>{{'label.file-upload.error.subtitle' | translate}}</b>
</h4>
</div> -->
<div class="col-12">
<div class="example-container mat-elevation-z8">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef> {{ 'header.' + column | translate}} </th>
<td mat-cell *matCellDef="let element"> {{element[column] | translate}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>
</div>
</mat-dialog-content>
<!-- <mat-dialog-actions align="end">
<button mat-button mat-dialog-close>{{'btn.cancel' | translate}}</button>
<button mat-button (click)="retry()" *ngIf="fileErrorDetail">{{'btn.retry' | translate}}</button>
<button mat-button (click)="ok()" cdkFocusInitial *ngIf="!fileErrorDetail"
[disabled]="formGroup.invalid || !enabledOK">{{'btn.accept' | translate}}</button>
</mat-dialog-actions> -->
<div mat-dialog-actions class="border-top">
<button mat-button mat-dialog-close>{{'btn.cancel' | translate}}</button>
<button mat-button (click)="retry()" *ngIf="fileErrorDetail">{{'btn.retry' | translate}}</button>
<button mat-button (click)="ok()" cdkFocusInitial *ngIf="!fileErrorDetail"
[disabled]="formGroup.invalid || !enabledOK">{{'btn.accept' | translate}}</button>
</div>
\ No newline at end of file
.example-container {
max-height: 200px;
overflow: auto;
margin-bottom: 10px
}
table {
width: 100%;
}
.none-overflow{
overflow: hidden;
}
.title_header_popup {
i {
font-size: 28px;
padding-right: 20px;
color: red;
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CaFileUploadModalComponent } from './ca-file-upload-modal.component';
describe('CaFileUploadModalComponent', () => {
let component: CaFileUploadModalComponent;
let fixture: ComponentFixture<CaFileUploadModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CaFileUploadModalComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CaFileUploadModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Inject } from '@angular/core';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { HttpEventType, HttpErrorResponse } from '@angular/common/http';
import { of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { AgentService } from '../../../service/agent.service';
import { MatDialogRef, MatTableDataSource, MAT_DIALOG_DATA } from '@angular/material';
import { NotificationService, NotificationType } from '@xdf/commons';
import { TranslateService } from '@ngx-translate/core';
export class FileUploadModel {
id: number;
uuid: string;
filename: string;
status: string;
user: string;
uploadDate: string;
description: string;
}
@Component({
selector: 'byte-ca-file-upload-modal',
templateUrl: './ca-file-upload-modal.component.html',
styleUrls: ['./ca-file-upload-modal.component.scss']
})
export class CaFileUploadModalComponent implements OnInit {
formGroup: FormGroup;
accept = '.xls,.xlsx';
enabledOK = false;
createMode = true;
fileInfo = new FileUploadModel();
fileErrorDetail: any;
dataSource = new MatTableDataSource([]);
displayedColumns: string[] = [];
applicationSettings: any;
constructor(
private formBuilder: FormBuilder,
private agentService: AgentService,
private notificationService: NotificationService,
private translateService: TranslateService,
public dialogRef: MatDialogRef<CaFileUploadModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.formGroup = this.formBuilder.group({
description: new FormControl({ value: '', disabled: false }),
file: new FormControl({ value: '', disabled: false }),
filename: new FormControl({ value: '', disabled: true })
});
if (data) {
this.fileInfo = data;
this.formGroup.controls.description.setValue(this.fileInfo.description);
this.formGroup.controls.filename.setValue(this.fileInfo.filename);
this.createMode = false;
this.enabledOK = true;
}
}
ngOnInit() {
}
buildErrorDetail(status, detail) {
this.fileErrorDetail = undefined;
if ('CONTENT_ERROR' === status) {
this.displayedColumns = ['line', 'type', 'value'];
if (detail) {
const fileErrorDetailTemp = [];
for (const line of Object.keys(detail)) {
const object = detail[line];
const type = Object.keys(object)[0];
fileErrorDetailTemp.push({
line,
type,
value: object[type],
});
}
if (fileErrorDetailTemp) {
this.fileErrorDetail = fileErrorDetailTemp;
this.dataSource.data = fileErrorDetailTemp;
}
}
} else if ('HEADER_ERROR' === status) {
this.displayedColumns = ['header', 'value'];
if (detail) {
const fileErrorDetailTemp = [];
for (const header of Object.keys(detail)) {
const value = detail[header];
fileErrorDetailTemp.push({
header,
value,
});
}
if (fileErrorDetailTemp) {
this.fileErrorDetail = fileErrorDetailTemp;
this.dataSource.data = fileErrorDetailTemp;
}
}
} else {
this.fileErrorDetail = undefined;
}
}
retry() {
this.fileErrorDetail = undefined;
}
uploadFile(event) {
for (const i in event) {
if (event[i] instanceof File) {
const reader = new FileReader();
reader.onload = () => {
// this.formGroup.controls['imageAvatar'].setValue(reader.result as string);
};
reader.readAsDataURL(event[i]);
const fileSelected = {
data: event[i],
state: 'in',
inProgress: false,
progress: 0,
canRetry: false
};
this.validateFile(fileSelected);
}
}
}
validateFile(file) {
const formData = new FormData();
formData.append('file', file.data);
file.inProgress = true;
this.enabledOK = false;
this.agentService.upload(formData).pipe(
map(event => {
switch (event.type) {
case HttpEventType.UploadProgress:
file.progress = Math.round(event.loaded * 100 / event.total);
break;
case HttpEventType.Response:
return event;
}
}),
catchError((error: HttpErrorResponse) => {
file.inProgress = false;
return of(`${file.data.name} upload failed.`);
})
).subscribe((event: any) => {
if (typeof (event) === 'object') {
this.fileErrorDetail = undefined;
this.enabledOK = false;
const info = event.body;
this.fileInfo = new FileUploadModel();
if (info) {
const status = info.fileValidationResult.status;
if ('OK' === status) {
this.fileInfo.id = info.id;
this.fileInfo.description = this.formGroup.controls.description.value;
this.fileInfo.uuid = info.uuid;
this.fileInfo.filename = info.fileName;
this.fileInfo.status = info.status;
this.fileInfo.user = info.user;
this.fileInfo.uploadDate = info.uploadDate;
this.enabledOK = true;
this.fileErrorDetail = undefined;
} else if ('INCOMPATIBLE_EXTENSION' === status) {
this.formGroup.controls.file.setValue(null);
const message = 'label.file.incompatible.extension';
this.enabledOK = false;
this.fileErrorDetail = undefined;
this.notificationService.showMessage(this.translateService.instant(message)
+ '[' + this.accept + ']', null, NotificationType.error);
} else if ('CONTENT_ERROR' === status) {
this.formGroup.controls.file.setValue(null);
this.enabledOK = false;
this.buildErrorDetail(status, info.fileValidationResult.recordsErrorMap);
} else if ('HEADER_ERROR' === status) {
this.formGroup.controls.file.setValue(null);
this.enabledOK = false;
this.buildErrorDetail(status, info.fileValidationResult.headersErrorMap);
}
} else {
this.enabledOK = false;
this.fileErrorDetail = undefined;
this.formGroup.controls.file.setValue(null);
}
}
});
}
ok() {
this.fileInfo.description = this.formGroup.controls.description.value;
this.dialogRef.close(this.fileInfo);
}
}
<div class="panel-viewer-wrapper">
<div class="panel-viewer-title">
<mat-icon>dns</mat-icon>
{{'label.questions.title' | translate}}
</div>
<div class="panel-viewer-body">
<div class="row pb-4">
<div class="col-1"></div>
<div class="col-10">
<div class="row pt-2" *ngIf="!viewMode" [hidden]="!(dataSource.data && dataSource.data.length)">
<div class="col-12">
<button type="button" class="btn btn-outline-primary btn-sm pull-right" (click)="addFile()">
<i class="fa fa-file-excel-o"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'btn.new.file' | translate}}
</span>
</button>
</div>
</div>
<div class="border-all text-center" *ngIf="!(dataSource.data && dataSource.data.length)">
<button type="button" class="btn btn-outline-primary btn-sm " (click)="addFile()">
<i class="fa fa-file-excel-o"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'btn.upload.file' | translate}}
</span>
</button>
</div>
<div class="table-container mat-elevation-z8 p-b-15 mt-3" [hidden]="!(dataSource.data && dataSource.data.length)">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Load ID -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="w-id text-right">{{'label.file.id' | translate}}</th>
<td mat-cell *matCellDef="let element" class="w-id text-right"> {{element.id}} </td>
</ng-container>
<!-- Filename -->
<ng-container matColumnDef="filename">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="w-filename">{{'label.file.name' | translate}}
</th>
<td mat-cell *matCellDef="let element" class="w-filename"> {{element.filename}} </td>
</ng-container>
<!-- Description -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="w-description">
{{'label.file.description' | translate}}</th>
<td mat-cell *matCellDef="let element" class="w-description"> {{element.description}} </td>
</ng-container>
<!-- Status -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="w-status">{{'label.file.status' | translate}}
</th>
<td mat-cell *matCellDef="let element" class="w-status">
<span [ngClass]="getClassStatus(element.status)">{{ getTextStatus(element.status) }}</span>
</td>
</ng-container>
<!-- User -->
<ng-container matColumnDef="user">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="w-user">{{'label.file.user' | translate}}
</th>
<td mat-cell *matCellDef="let element" class="w-user"> {{element.user}} </td>
</ng-container>
<!-- Fecha de carga -->
<ng-container matColumnDef="uploadDate">
<th mat-header-cell *matHeaderCellDef class="w-uploadDate text-right">{{'label.file.upload.date' | translate}}</th>
<td mat-cell *matCellDef="let element" class="w-uploadDate text-right">
<i class="fa fa-calendar" *ngIf="element.uploadDate"></i> {{element.uploadDate}}
</td>
</ng-container>
<!-- Actions -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="w-actions"></th>
<td mat-cell *matCellDef="let element; let $index = index" class="w-actions text-center">
<div class="btn-group">
<button type="button" class="btn btn-default btn-sm" (click)="onEditRecord(element, $index)"
[disabled]="!resourceAuth['edit'] || viewMode">
<i class="fa fa-edit"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'btn.edit' | translate}}
</span>
</button>
<button type="button" class="btn btn-default btn-sm" (click)="onDeleteRecord(element, $index)"
[disabled]="!resourceAuth['delete'] || viewMode">
<i class="fa fa-trash"></i>
<span class="visible-md-inline visible-lg-inline visible-xl-inline">
{{'btn.delete' | translate}}
</span>
</button>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>
</div>
<div class="col-1"></div>
</div>
<div class="footer-bar">
<div class="row">
<div class="col-12">
<button mat-stroked-button matStepperPrevious class="pull-left">
<i class="fa fa-chevron-left"></i>
<span class="pl-1">{{'btn.previous' | translate}}</span>
</button>
<button mat-stroked-button (click)="next()" class="pull-right" [disabled]="!dataSource.data.length">
<span class="pr-1">{{'btn.next' | translate}}</span>
<i class="fa fa-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
\ No newline at end of file
.table-container {
height: 300px;
overflow: auto;
}
th.mat-sort-header-sorted {
color: black;
}
$fontcolor: #676a6c;
.content {
width: 100%;
display: flex;
padding-right: 0px !important;
.grid-container,
.filter-panel {
.crud-table {
margin-bottom: 0px;
width: 100%;
}
.form {
padding: 10px 20px 10px 20px;
}
}
.filter-panel {
margin-bottom: 10px;
}
}
.btn-group {
.btn {
font-size: 10px;
}
}
th.mat-column-actions {
width: 1px !important;
padding-right: 0px !important;
}
td.mat-column-actions {
min-width: 80px;
padding-right: 20px !important;
cursor: default !important;
}
td {
vertical-align: middle !important;
color: $fontcolor;
font-size: 12px;
}
th {
vertical-align: middle !important;
}
tr.mat-header-row {
height: 50px !important;
}
tr.mat-footer-row,
tr.mat-row {
height: 40px !important;
}
.table-toolbar {
right: 10px;
top: 10px;
button {
font-size: 12px;
}
}
.mat-raised-button {
padding: 0px 10px;
}
::ng-deep .mat-sort-header-arrow {
visibility: hidden;
}
::ng-deep .mat-sort-header-button {
.mat-icon {
padding-left: 10px;
font-size: 12px;
padding-top: 5px;
}
}
th {
background: rgb(242, 242, 242);
}
//////
table {
width: 100%;
}
tr.mat-header-row {
height: 25px;
}
tr.mat-footer-row,
tr.mat-row {
height: 40px;
}
.button-grid {
margin: 2px 5px;
mat-icon {
padding-top: 2px;
}
}
.btn-grid-action {
padding: 0 10px;
line-height: 0em;
}
.grid-ibox-content {
width: 100%;
}
td.mat-cell,
td.mat-footer-cell,
th.mat-header-cell {
/* padding: 0; */
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 1px 10px 1px 10px;
}
.p-b-15 {
padding-bottom: 15px;
}
.w-id {
width: 5%;
}
.w-filename {
width: 20%;
}
.w-description {
width: 18%;
}
.w-status {
width: 12%;
}
.w-user {
width: 15%;
}
.w-uploadDate {
width: 10%;
}
.w-actions {
width: 20%;
}
.border-all.text-center {
padding: 50px;
border-radius: 4px;
margin-top: 56px;
}
::ng-deep .text-right > .mat-sort-header-container {
justify-content: flex-end !important;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CaFrequentQuestionsComponent } from './ca-frequent-questions.component';
describe('CaFrequentQuestionsComponent', () => {
let component: CaFrequentQuestionsComponent;
let fixture: ComponentFixture<CaFrequentQuestionsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CaFrequentQuestionsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CaFrequentQuestionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input, ViewContainerRef, ViewChild, Output } from '@angular/core';
import { AfterViewInit, EventEmitter } from '@angular/core';
import { MatStepper, MatDialog, MatTableDataSource, MatSort } from '@angular/material';
import { ActivatedRoute, Router } from '@angular/router';
import { animate, trigger, state, transition, style } from '@angular/animations';
import { AuthorizationService } from '@xdf/security';
import { TranslateService } from '@ngx-translate/core';
import { CaFileUploadModalComponent, FileUploadModel } from '../ca-file-upload-modal/ca-file-upload-modal.component';
@Component({
selector: 'byte-ca-frequent-questions',
templateUrl: './ca-frequent-questions.component.html',
styleUrls: ['./ca-frequent-questions.component.scss'],
animations: [
trigger('fadeInOut', [
state('in', style({ opacity: 100 })),
transition('* => void', [
animate(300, style({ opacity: 0 }))
])
])
]
})
export class CaFrequentQuestionsComponent implements OnInit, AfterViewInit {
@ViewChild(MatSort, { static: false }) sort: MatSort;
@Input()
stepper: MatStepper;
@Output()
nextPage: EventEmitter<boolean> = new EventEmitter<boolean>();
public resourceAuth = new Object();
dirty = false;
accept = '.xls,.xlsx';
agentDetail: any;
displayedColumns: string[] = ['id', 'filename', 'description', 'status', 'user', 'uploadDate', 'actions'];
dataSource = new MatTableDataSource<FileUploadModel>([]);
mapStatus = {
LO: {
class: 'label label-primary',
text: 'label.status.loaded'
},
PS: {
class: 'label label-warning',
text: 'label.status.pending'
}
};
files = [];
viewMode: boolean;
constructor(
protected route: ActivatedRoute,
protected router: Router,
protected vcRef: ViewContainerRef,
protected authorizationService: AuthorizationService,
private translate: TranslateService,
private activatedRoute: ActivatedRoute,
private matDialog: MatDialog
) {
this.viewMode = this.activatedRoute.snapshot.data.mode === 'view';
}
ngOnInit() {
const keyAuthorization = 'authorization';
const authList = this.route.snapshot.paramMap[keyAuthorization];
if (authList) {
this.resourceAuth = new Object();
authList.forEach(option => {
this.resourceAuth[option] = true;
});
}
}
isDirty() {
return this.dirty;
}
buildForm(agentDetail: any) {
this.agentDetail = agentDetail;
if (agentDetail.frequentQuestions) {
this.dataSource.data = agentDetail.frequentQuestions;
}
}
setDataForWizard(result: any) {
console.log(result);
/*
this.protocols = result.connectionProtocolOptions;
this.generalProperties = result.generalProperties;
this.alias = result.alias;
this.maxNumberOfConnectionsByExternalSystem = result.maxNumberOfConnectionsByExternalSystem;
this.columnsForEntity = result.columnsForEntity;
*/
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
getClassStatus(status) {
return this.mapStatus[status].class;
}
getTextStatus(status) {
return this.translate.instant(this.mapStatus[status].text);
}
addFile() {
const dialog = this.matDialog.open(CaFileUploadModalComponent, {
width: '600px',
data: null
});
dialog.afterClosed().subscribe(result => {
if (result) {
const fileAdded = { ...result };
const listTemp = this.dataSource.data;
listTemp.push(fileAdded);
this.dataSource.data = listTemp;
this.dirty = true;
}
});
}
onEditRecord(item, index) {
item.editable = true;
const dialog = this.matDialog.open(CaFileUploadModalComponent, {
width: '500px',
data: item
});
dialog.afterClosed().subscribe(result => {
if (result) {
const fileEdited = { ...result };
const listTemp = this.dataSource.data;
listTemp[index] = fileEdited;
this.dataSource.data = listTemp;
}
});
}
saveQuestions(validateForm: boolean) {
const success = true;
this.agentDetail.frequentQuestions = this.dataSource.data;
return success;
}
onDeleteRecord(item, index) {
const listTemp = this.dataSource.data;
listTemp.splice(index, 1);
this.dataSource.data = listTemp;
this.dirty = true;
}
next() {
if (!this.dataSource.data.length) {
return;
}
if (this.viewMode) {
this.stepper.next();
return;
}
if (this.saveQuestions(true)) {
this.nextPage.emit(this.isDirty());
this.stepper.next();
}
}
}
ul,
li {
margin: 0;
padding: 0;
list-style: none;
}
#file-label {
display: inline-flex;
vertical-align: middle;
font-size: 12px;
line-height: 18px;
}
#file-label mat-icon {
font-size: 18px;
text-align: center;
}
#file-label a {
cursor: pointer;
}
.uploadList {
margin-top: 10px;
}
.uploadfilecontainer {
background-position: center;
line-height: 200px;
margin: 20px auto;
border: 2px dashed #1c8adb;
border-radius: 10px;
button {
vertical-align: middle;
}
}
.uploadfilecontainer:hover {
cursor: pointer;
background-color: #9ecbec !important;
opacity: 0.8;
}
.image-preview {
height: 100%;
width: 100%;
position: relative;
overflow: hidden;
background-image: none;
background-color: white;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 5px;
}
.image-notfound {
width: 100%;
height: 100%;
// border-radius: 5px;
// border-bottom-color:rgba(0,0,0,.12);
&::after {
content: "photo_size_select_actual";
font-family: "Material Icons";
position: relative;
font-size: 4.5em;
color: rgba(230, 230, 230, 1);
top: calc(50% - 3rem);
left: calc(50% - 2.25rem);
z-index: 0;
}
}
.btn-upload-image {
font-size: 80%;
width: 100%;
border-radius: 0;
border-width: 1px 0 0 0;
border-top-color: rgba(0, 0, 0, 0.12);
}
.form-field-label {
color: rgba(0, 0, 0, 0.54);
font-size: 75%;
}
.with-image {
text-align: center;
height: 100%;
}
.edit-image {
text-align: center;
height: calc(100% - 36px);
}
.without-image {
text-align: inherit;
height: calc(100% - 36px);
}
.h-85 {
height: 85%;
}
.h-100 {
height: 85%;
}
.col-6.mat-elevation-z3 {
padding: 24px;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CaGeneralInformationComponent } from './ca-general-information.component';
describe('CaGeneralInformationComponent', () => {
let component: CaGeneralInformationComponent;
let fixture: ComponentFixture<CaGeneralInformationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CaGeneralInformationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CaGeneralInformationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { MatStepper } from '@angular/material';
import { ActivatedRoute } from '@angular/router';
import { animate, trigger, state, transition, style } from '@angular/animations';
import { NotificationService, NotificationType } from '@xdf/commons';
import { TranslateService } from '@ngx-translate/core';
import { ValidatorUtils } from '@xdf/gallery';
export const FILE_TYPE = {
image: /image.*/,
file: /.*/
};
@Component({
selector: 'byte-ca-general-information',
templateUrl: './ca-general-information.component.html',
styleUrls: ['./ca-general-information.component.scss'],
animations: [
trigger('fadeInOut', [
state('in', style({ opacity: 100 })),
transition('* => void', [
animate(300, style({ opacity: 0 }))
])
])
]
})
export class CaGeneralInformationComponent implements OnInit {
@Input()
stepper: MatStepper;
@Output()
nextPage: EventEmitter<boolean> = new EventEmitter<boolean>();
agentDetail: any;
formGroup: FormGroup;
viewMode: boolean;
mode = 'new';
imageName = 'imageAvatar';
urlBase = '';
countries = [];
timezones = [];
languages = [
{
code: 'ES',
name: 'Español'
}
];
types = [
{
code: 'FQ',
name: 'Agente de preguntas frecuentes'
}
];
acceptedImageTypes = ['image/png', 'image/jpeg', 'image/gif'];
acceptedTypes = 'PNG, JPEG, GIF';
constructor(
private formBuilder: FormBuilder,
private activatedRoute: ActivatedRoute,
private notificationService: NotificationService,
private translateService: TranslateService,
private validatorUtils: ValidatorUtils
) {
this.mode = this.activatedRoute.snapshot.data.mode;
this.viewMode = this.mode === 'view';
this.countries = this.activatedRoute.snapshot.data.countryData;
this.formGroup = this.formBuilder.group({
name: new FormControl({ value: '', disabled: this.viewMode }),
description: new FormControl({ value: '', disabled: this.viewMode }),
version: new FormControl({ value: '', disabled: this.viewMode }),
imageAvatar: new FormControl({ value: '', disabled: this.viewMode }, { validators: [Validators.required] }),
country: new FormControl({ value: '', disabled: this.viewMode }),
timezone: new FormControl({ value: '', disabled: this.viewMode }),
language: new FormControl({ value: '', disabled: this.viewMode }),
type: new FormControl({ value: '', disabled: this.viewMode })
});
}
ngOnInit() {
}
buildForm(agentDetail: any) {
this.agentDetail = agentDetail;
if (agentDetail) {
this.formGroup.patchValue({
name: agentDetail.name,
description: agentDetail.description,
version: agentDetail.version,
timezone: agentDetail.timezone,
country: agentDetail.countryId,
imageAvatar: agentDetail.avatar,
language: agentDetail.language,
type: agentDetail.type
});
this.onCountryChange(this.formGroup.controls.country);
}
}
saveGeneralInformation(validateForm: boolean): boolean {
if (validateForm && !this.formGroup.valid) {
return false;
}
this.agentDetail.name = this.formGroup.controls.name.value;
this.agentDetail.description = this.formGroup.controls.description.value;
this.agentDetail.version = this.formGroup.controls.version.value;
this.agentDetail.countryId = this.formGroup.controls.country.value;
this.agentDetail.timezone = this.formGroup.controls.timezone.value;
this.agentDetail.avatar = this.formGroup.controls.imageAvatar.value;
this.agentDetail.language = this.formGroup.controls.language.value;
this.agentDetail.type = this.formGroup.controls.type.value;
return true;
}
isDirty() {
return this.formGroup.dirty;
}
uploadFiles(event) {
for (const i in event) {
if (event[i] instanceof File) {
const eventTemp = event[i];
if (this.acceptedImageTypes.includes(eventTemp.type)) {
const reader = new FileReader();
reader.onload = () => {
this.formGroup.controls['imageAvatar'].setValue(reader.result as string);
};
reader.readAsDataURL(event[i]);
} else {
const message = 'label.imageAvatar.incompatible.extension';
this.notificationService.showMessage(this.translateService.instant(message) +
'[' + this.acceptedTypes + ']', null, NotificationType.error);
}
}
}
}
onCountryChange(obj, deleteTimeZone?: boolean) {
const selectedCountry = obj.value;
this.timezones = [];
if (deleteTimeZone) {
this.formGroup.controls['timezone'].setValue(null);
}
if (selectedCountry) {
for (const index in this.countries) {
if (selectedCountry === this.countries[index].id) {
this.timezones = this.countries[index].timezones;
break;
}
}
}
}
next() {
if (this.viewMode) {
this.stepper.next();
return;
}
if (this.saveGeneralInformation(true)) {
this.nextPage.emit(this.isDirty());
this.stepper.next();
}
}
getErrors(name: string, placeholder: string) {
return this.validatorUtils.getErrors(this.formGroup.controls[name] as FormControl, name, placeholder);
}
}
import { TestBed } from '@angular/core/testing';
import { UploadService } from './upload.service';
describe('UploadService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: UploadService = TestBed.get(UploadService);
expect(service).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'byte-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment