Added Azure AD administration routines.

This commit is contained in:
Rodrigo Rodriguez 2018-09-16 17:00:17 -03:00
parent e7a7fcedbf
commit c03228dbbe
18 changed files with 617 additions and 374 deletions

View file

@ -30,33 +30,33 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict" "use strict";
const UrlJoin = require("url-join") const UrlJoin = require("url-join");
import { AzureSearch } from "pragmatismo-io-framework" import { AzureSearch } from "pragmatismo-io-framework";
import { GBMinInstance } from "botlib" import { GBMinInstance } from "botlib";
import { IGBDialog } from "botlib" import { IGBDialog } from "botlib";
import { GBDeployer } from '../../core.gbapp/services/GBDeployer' import { GBDeployer } from "../../core.gbapp/services/GBDeployer";
import { GBImporter } from '../../core.gbapp/services/GBImporter' import { GBImporter } from "../../core.gbapp/services/GBImporter";
import { GBConfigService } from '../../core.gbapp/services/GBConfigService' import { GBConfigService } from "../../core.gbapp/services/GBConfigService";
import { KBService } from './../../kb.gbapp/services/KBService' import { KBService } from "./../../kb.gbapp/services/KBService";
import { BotAdapter } from "botbuilder" import { BotAdapter } from "botbuilder";
import { GBAdminService } from "../services/GBAdminService";
/** /**
* Dialogs for administration tasks. * Dialogs for administration tasks.
*/ */
export class AdminDialog extends IGBDialog { export class AdminDialog extends IGBDialog {
static async undeployPackageCommand(text: any, min: GBMinInstance, dc) { static async undeployPackageCommand(text: any, min: GBMinInstance, dc) {
let packageName = text.split(" ")[1] let packageName = text.split(" ")[1];
let importer = new GBImporter(min.core) let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer) let deployer = new GBDeployer(min.core, importer);
dc.context.sendActivity(`Undeploying package ${packageName}...`) dc.context.sendActivity(`Undeploying package ${packageName}...`);
await deployer.undeployPackageFromLocalPath( await deployer.undeployPackageFromLocalPath(
min.instance, min.instance,
UrlJoin("deploy", packageName)) UrlJoin("deploy", packageName)
dc.context.sendActivity(`Package ${packageName} undeployed...`) );
dc.context.sendActivity(`Package ${packageName} undeployed...`);
} }
static async deployPackageCommand( static async deployPackageCommand(
@ -65,11 +65,17 @@ export class AdminDialog extends IGBDialog {
deployer: GBDeployer, deployer: GBDeployer,
min: GBMinInstance min: GBMinInstance
) { ) {
let packageName = text.split(" ")[1] let packageName = text.split(" ")[1];
await dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`) await dc.context.sendActivity(
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH") `Deploying package ${packageName}... (It may take a few seconds)`
await deployer.deployPackageFromLocalPath(UrlJoin(additionalPath, packageName)) );
await dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`) let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
await deployer.deployPackageFromLocalPath(
UrlJoin(additionalPath, packageName)
);
await dc.context.sendActivity(
`Package ${packageName} deployed... Please run rebuildIndex command.`
);
} }
static async rebuildIndexCommand(min: GBMinInstance, dc) { static async rebuildIndexCommand(min: GBMinInstance, dc) {
@ -78,12 +84,15 @@ export class AdminDialog extends IGBDialog {
min.instance.searchHost, min.instance.searchHost,
min.instance.searchIndex, min.instance.searchIndex,
min.instance.searchIndexer min.instance.searchIndexer
) );
dc.context.sendActivity("Rebuilding index...") dc.context.sendActivity("Rebuilding index...");
await search.deleteIndex() await search.deleteIndex();
let kbService = new KBService(min.core.sequelize) let kbService = new KBService(min.core.sequelize);
await search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb") await search.createIndex(
await dc.context.sendActivity("Index rebuilt.") kbService.getSearchSchema(min.instance.searchIndex),
"gb"
);
await dc.context.sendActivity("Index rebuilt.");
} }
/** /**
@ -93,80 +102,102 @@ export class AdminDialog extends IGBDialog {
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
// Setup services. // Setup services.
let importer = new GBImporter(min.core) let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer) let deployer = new GBDeployer(min.core, importer);
min.dialogs.add("/admin", [ min.dialogs.add("/admin", [
async dc => {
await AdminDialog.refreshAdminToken(min, dc);
// await dc.context.sendActivity(
// `Deploying package ... (It may take a few seconds)`
// );
// await AdminDialog.deployPackageCommand(
// "deployPackage ProjectOnline.gbkb",
// dc,
// deployer,
// min
// );
await dc.endAll();
}
]);
async (dc) => { min.dialogs.add("/adminUpdateToken", [
async (dc, args, next) => {
await dc.context.sendActivity(`Deploying package ... (It may take a few seconds)`) await dc.endAll();
await AdminDialog.deployPackageCommand("deployPackage ProjectOnline.gbkb", dc, deployer, min) let service = new GBAdminService();
await dc.endAll() await service.saveValue("authenticatorToken", args.token)
await dc.context.sendActivity("Token has been updated.");
}]) await dc.replace("/ask")
}
]);
min.dialogs.add("/admin1", [ min.dialogs.add("/admin1", [
async (dc, args) => { async (dc, args) => {
const prompt = "Please, authenticate:" const prompt = "Please, authenticate:";
await dc.prompt('textPrompt', prompt) await dc.prompt("textPrompt", prompt);
}, },
async (dc, value) => { async (dc, value) => {
let text = value let text = value;
const user = min.userState.get(dc.context) const user = min.userState.get(dc.context);
if ( if (!user.authenticated || text === GBConfigService.get("ADMIN_PASS")) {
!user.authenticated || user.authenticated = true;
text === GBConfigService.get("ADMIN_PASS")
) {
user.authenticated = true
await dc.context.sendActivity( await dc.context.sendActivity(
"Welcome to Pragmatismo.io GeneralBots Administration." "Welcome to Pragmatismo.io GeneralBots Administration."
) );
await dc.prompt('textPrompt', "Which task do you wanna run now?") await dc.prompt("textPrompt", "Which task do you wanna run now?");
} else { } else {
await dc.endAll() await dc.endAll();
} }
}, },
async (dc, value) => { async (dc, value) => {
var text = value var text = value;
const user = min.userState.get(dc.context) const user = min.userState.get(dc.context);
if (text === "quit") { if (text === "quit") {
user.authenticated = false user.authenticated = false;
await dc.replace("/") await dc.replace("/");
} else if (text === "sync") { } else if (text === "sync") {
await min.core.syncDatabaseStructure() await min.core.syncDatabaseStructure();
await dc.context.sendActivity("Sync started...") await dc.context.sendActivity("Sync started...");
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "rebuildIndex") { } else if (text.split(" ")[0] === "rebuildIndex") {
await AdminDialog.rebuildIndexCommand(min, dc) await AdminDialog.rebuildIndexCommand(min, dc);
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "deployPackage") { } else if (text.split(" ")[0] === "deployPackage") {
await AdminDialog.deployPackageCommand(text, dc, deployer, min) await AdminDialog.deployPackageCommand(text, dc, deployer, min);
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "redeployPackage") { } else if (text.split(" ")[0] === "redeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc) await AdminDialog.undeployPackageCommand(text, min, dc);
await AdminDialog.deployPackageCommand(text, dc, deployer, min) await AdminDialog.deployPackageCommand(text, dc, deployer, min);
await dc.context.sendActivity("Redeploy done.") await dc.context.sendActivity("Redeploy done.");
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "undeployPackage") { } else if (text.split(" ")[0] === "undeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc) await AdminDialog.undeployPackageCommand(text, min, dc);
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "applyPackage") { } else if (text.split(" ")[0] === "applyPackage") {
await dc.context.sendActivity("Applying in progress...") await dc.context.sendActivity("Applying in progress...");
await min.core.loadInstance(text.split(" ")[1]) await min.core.loadInstance(text.split(" ")[1]);
await dc.context.sendActivity("Applying done...") await dc.context.sendActivity("Applying done...");
await dc.replace("/admin", { firstRun: false }) await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "rat") { } else if (text.split(" ")[0] === "rat") {
await min.conversationalService.sendEvent(dc, "play", { playerType: "login", data: null }) await AdminDialog.refreshAdminToken(min, dc);
await dc.context.sendActivity("Realize login clicando no botão de login, por favor...")
} }
} }
]) ]);
}
private static async refreshAdminToken(min: any, dc: any) {
let config = {
authenticatorTenant: min.instance.authenticatorTenant,
authenticatorClientID: min.instance.authenticatorClientID
};
await min.conversationalService.sendEvent(dc, "play", {
playerType: "login",
data: config
});
await dc.context.sendActivity("Update your Administrative token by Login...");
} }
} }

View file

@ -38,11 +38,15 @@ import { AdminDialog } from './dialogs/AdminDialog'
import { GBMinInstance, IGBPackage, IGBCoreService } from 'botlib' import { GBMinInstance, IGBPackage, IGBCoreService } from 'botlib'
import { Sequelize } from 'sequelize-typescript' import { Sequelize } from 'sequelize-typescript'
import { GuaribasAdmin } from './models/AdminModel';
export class GBAdminPackage implements IGBPackage { export class GBAdminPackage implements IGBPackage {
sysPackages: IGBPackage[] = null sysPackages: IGBPackage[] = null
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasAdmin
])
} }
unloadPackage(core: IGBCoreService): void { unloadPackage(core: IGBCoreService): void {

View file

@ -0,0 +1,61 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
"use strict";
import {
Table,
Column,
Model,
CreatedAt,
UpdatedAt,
} from "sequelize-typescript";
@Table
export class GuaribasAdmin extends Model<GuaribasAdmin>
{
@Column
key: string;
@Column
value: string;
@Column
@CreatedAt
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date;
}

View file

@ -0,0 +1,57 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
"use strict"
import { GuaribasAdmin } from "../models/AdminModel";
export class GBAdminService {
async saveValue(key: string, value: string): Promise<GuaribasAdmin> {
let options = { where: {} }
options.where = { key: key }
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
return admin.save()
}
async getValue(key: string) {
let options = { where: {} }
options.where = { key: key }
let obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value);
}
}

View file

@ -54,14 +54,15 @@ export class WelcomeDialog extends IGBDialog {
user.once = true; user.once = true;
var a = new Date(); var a = new Date();
const date = a.getHours(); const date = a.getHours();
var msg = 4; var msg =
date < 12 date < 12
? Messages[locale].good_morning ? Messages[locale].good_morning
: date < 18 : date < 18
? Messages[locale].good_evening ? Messages[locale].good_evening
: Messages[locale].good_night; : Messages[locale].good_night;
await dc.context.sendActivity(Messages[locale].hi(msg)); await dc.context.sendActivity(Messages[locale].hi(msg));
await dc.replace("/ask", { firstTime: true });
if ( if (
dc.context.activity && dc.context.activity &&

View file

@ -30,14 +30,14 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict" "use strict";
import { import {
DataTypes, DataTypes,
DataTypeUUIDv4, DataTypeUUIDv4,
DataTypeDate, DataTypeDate,
DataTypeDecimal DataTypeDecimal
} from "sequelize" } from "sequelize";
import { import {
Sequelize, Sequelize,
@ -54,170 +54,214 @@ import {
DataType, DataType,
PrimaryKey, PrimaryKey,
AutoIncrement AutoIncrement
} from "sequelize-typescript" } from "sequelize-typescript";
import { IGBInstance } from "botlib" import { IGBInstance } from "botlib";
@Table @Table
export class GuaribasInstance extends Model<GuaribasInstance> implements IGBInstance { export class GuaribasInstance extends Model<GuaribasInstance>
implements IGBInstance {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
instanceId: number instanceId: number;
@Column applicationPrincipal: string
@Column @Column
whoAmIVideo: string applicationPrincipal: string;
@Column botId: string @Column
whoAmIVideo: string;
@Column title: string @Column
botId: string;
@Column description: string @Column
title: string;
@Column version: string @Column
description: string;
@Column enabledAdmin: boolean @Column
version: string;
@Column
enabledAdmin: boolean;
/* Services section on bot.json */ /* Services section on bot.json */
@Column engineName: string @Column
engineName: string;
@Column marketplaceId: string
@Column textAnalyticsKey: string
@Column textAnalyticsServerUrl: string
@Column marketplacePassword: string
@Column webchatKey: string
@Column whatsappBotKey: string
@Column whatsappServiceKey: string
@Column whatsappServiceNumber: string
@Column whatsappServiceUrl: string
@Column whatsappServiceWebhookUrl: string
@Column speechKey: string
@Column spellcheckerKey: string
@Column theme: string
@Column ui: string
@Column kb: string
@Column @Column
nlpAppId: string marketplaceId: string;
@Column @Column
nlpSubscriptionKey: string textAnalyticsKey: string;
@Column
textAnalyticsServerUrl: string;
@Column
marketplacePassword: string;
@Column
webchatKey: string;
@Column
authenticatorTenant: string;
@Column
authenticatorSignUpSignInPolicy: string;
@Column
authenticatorClientID: string;
@Column
whatsappBotKey: string;
@Column
whatsappServiceKey: string;
@Column
whatsappServiceNumber: string;
@Column
whatsappServiceUrl: string;
@Column
whatsappServiceWebhookUrl: string;
@Column
smsKey: string;
@Column
smsSecret: string;
@Column
smsServiceNumber: string;
@Column
speechKey: string;
@Column
spellcheckerKey: string;
@Column
theme: string;
@Column
ui: string;
@Column
kb: string;
@Column
nlpAppId: string;
@Column
nlpSubscriptionKey: string;
@Column @Column
@Column({ type: DataType.STRING(512) }) @Column({ type: DataType.STRING(512) })
nlpServerUrl: string nlpServerUrl: string;
@Column searchHost: string @Column
searchHost: string;
@Column searchKey: string @Column
searchKey: string;
@Column searchIndex: string @Column
searchIndex: string;
@Column searchIndexer: string @Column
searchIndexer: string;
/* Settings section of bot.json */ /* Settings section of bot.json */
@Column(DataType.FLOAT) nlpVsSearch: number @Column(DataType.FLOAT)
nlpVsSearch: number;
@Column(DataType.FLOAT) searchScore: number @Column(DataType.FLOAT)
searchScore: number;
@Column(DataType.FLOAT) nlpScore: number @Column(DataType.FLOAT)
nlpScore: number;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date updatedAt: Date;
} }
@Table @Table
export class GuaribasPackage extends Model<GuaribasPackage> { export class GuaribasPackage extends Model<GuaribasPackage> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
packageId: number packageId: number;
@Column @Column
packageName: string packageName: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance instance: GuaribasInstance;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date updatedAt: Date;
} }
@Table @Table
export class GuaribasChannel extends Model<GuaribasChannel> { export class GuaribasChannel extends Model<GuaribasChannel> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
channelId: number channelId: number;
@Column title: string @Column
title: string;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date updatedAt: Date;
} }
@Table @Table
export class GuaribasException extends Model<GuaribasException> { export class GuaribasException extends Model<GuaribasException> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
exceptionId: number exceptionId: number;
@Column message: string @Column
message: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance instance: GuaribasInstance;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date updatedAt: Date;
} }

View file

@ -1,3 +1,4 @@
import { IGBInstance } from 'botlib';
/*****************************************************************************\ /*****************************************************************************\
| ( )_ _ | | ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
@ -41,6 +42,8 @@ import { LuisRecognizer } from "botbuilder-ai";
import { MessageFactory } from "botbuilder"; import { MessageFactory } from "botbuilder";
import { Messages } from "../strings"; import { Messages } from "../strings";
import { AzureText } from "pragmatismo-io-framework"; import { AzureText } from "pragmatismo-io-framework";
const Nexmo = require("nexmo");
export interface LanguagePickerSettings { export interface LanguagePickerSettings {
defaultLocale?: string; defaultLocale?: string;
@ -66,6 +69,19 @@ export class GBConversationalService implements IGBConversationalService {
return dc.context.sendActivity(msg); return dc.context.sendActivity(msg);
} }
async sendSms(min: GBMinInstance, mobile: string, text: string) : Promise<any> {
const nexmo = new Nexmo({
apiKey: min.instance.smsKey,
apiSecret: min.instance.smsSecret,
});
nexmo.message.sendSms(
min.instance.smsServiceNumber,
mobile,
text,
);
}
async routeNLP(dc: any, min: GBMinInstance, text: string): Promise<boolean> { async routeNLP(dc: any, min: GBMinInstance, text: string): Promise<boolean> {
// Invokes LUIS. // Invokes LUIS.

View file

@ -37,6 +37,7 @@ import { Sequelize } from "sequelize-typescript"
import { GBConfigService } from "./GBConfigService" import { GBConfigService } from "./GBConfigService"
import { IGBInstance, IGBCoreService } from "botlib" import { IGBInstance, IGBCoreService } from "botlib"
import { GuaribasInstance } from "../models/GBModel" import { GuaribasInstance } from "../models/GBModel"
import { GBAdminService } from "../../admin.gbapp/services/GBAdminService";
/** /**
* Core service layer. * Core service layer.
@ -47,6 +48,11 @@ export class GBCoreService implements IGBCoreService {
*/ */
public sequelize: Sequelize public sequelize: Sequelize
/**
* Administrative services.
*/
public adminService: GBAdminService
/** /**
* Allows filtering on SQL generated before send to the database. * Allows filtering on SQL generated before send to the database.
*/ */
@ -72,6 +78,7 @@ export class GBCoreService implements IGBCoreService {
*/ */
constructor() { constructor() {
this.dialect = GBConfigService.get("DATABASE_DIALECT") this.dialect = GBConfigService.get("DATABASE_DIALECT")
this.adminService = new GBAdminService();
} }
/** /**
@ -98,8 +105,8 @@ export class GBCoreService implements IGBCoreService {
let logging = let logging =
GBConfigService.get("DATABASE_LOGGING") === "true" GBConfigService.get("DATABASE_LOGGING") === "true"
? (str: string) => { ? (str: string) => {
logger.info(str) logger.info(str)
} }
: false : false
let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true" let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true"
@ -240,79 +247,38 @@ export class GBCoreService implements IGBCoreService {
} }
async syncDatabaseStructure() { async syncDatabaseStructure() {
return new Promise((resolve, reject) => { if (GBConfigService.get("DATABASE_SYNC") === "true") {
if (GBConfigService.get("DATABASE_SYNC") === "true") { const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"
const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true" const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"
const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true" logger.info("Syncing database...")
logger.info("Syncing database...") return this.sequelize.sync({
this.sequelize alter: alter,
.sync({ force: force
alter: alter, });
force: force } else {
}) let msg = "Database synchronization is disabled.";
.then( logger.info(msg)
value => { return Promise.reject(msg)
logger.info("Database synced.") }
resolve(value)
},
err => reject(err)
)
} else {
logger.info("Database synchronization is disabled.")
resolve()
}
})
} }
/** /**
* Loads all items to start several listeners. * Loads all items to start several listeners.
*/ */
async loadInstances(): Promise<IGBInstance> { async loadInstances(): Promise<IGBInstance> {
return new Promise((resolve, reject) => { return GuaribasInstance.findAll({});
GuaribasInstance.findAll({})
.then((items: IGBInstance[]) => {
if (!items) items = []
if (items.length == 0) {
resolve([])
} else {
resolve(items)
}
})
.catch(reason => {
if (reason.message.indexOf("no such table: GuaribasInstance") != -1) {
resolve([])
} else {
logger.info(`GuaribasServiceError: ${reason}`)
reject(reason)
}
})
})
} }
/** /**
* Loads just one Bot instance. * Loads just one Bot instance.
*/ */
async loadInstance(botId: string): Promise<IGBInstance> { async loadInstance(botId: string): Promise<IGBInstance> {
return new Promise<IGBInstance>((resolve, reject) => { let options = { where: {} }
let options = { where: {} }
if (botId != "[default]") { if (botId != "[default]") {
options.where = { botId: botId } options.where = { botId: botId }
} }
GuaribasInstance.findOne(options) return GuaribasInstance.findOne(options);
.then((instance: IGBInstance) => {
if (instance) {
resolve(instance)
} else {
resolve(null)
}
})
.catch(err => {
logger.info(`GuaribasServiceError: ${err}`)
reject(err)
})
})
} }
} }

View file

@ -134,7 +134,7 @@ export class GBDeployer {
appPackagesProcessed++ appPackagesProcessed++
}) })
.catch(err => { .catch(err => {
logger.info(`Error deploying App (.gbapp): ${e}: ${err}`) logger.error(`Error deploying App (.gbapp): ${e}: ${err}`)
appPackagesProcessed++ appPackagesProcessed++
}) })
} else { } else {
@ -145,14 +145,16 @@ export class GBDeployer {
WaitUntil() WaitUntil()
.interval(1000) .interval(1000)
.times(10) .times(10)
.condition(function(cb) { .condition(function (cb) {
logger.info(`Waiting for app package deployment...`) logger.info(`Waiting for app package deployment...`)
cb(appPackagesProcessed == gbappPackages.length) cb(appPackagesProcessed == gbappPackages.length)
}) })
.done(function(result) { .done(function (result) {
logger.info(`App Package deployment done.`) logger.info(`App Package deployment done.`);
core.syncDatabaseStructure() (async () => {
await core.syncDatabaseStructure()
})()
/** Deploys all .gbot files first. */ /** Deploys all .gbot files first. */
@ -179,7 +181,7 @@ export class GBDeployer {
server.use("/themes/" + filenameOnly, express.static(filename)) server.use("/themes/" + filenameOnly, express.static(filename))
logger.info( logger.info(
`Theme (.gbtheme) assets accessible at: ${"/themes/" + `Theme (.gbtheme) assets accessible at: ${"/themes/" +
filenameOnly}.` filenameOnly}.`
) )
/** Knowledge base for bots. */ /** Knowledge base for bots. */
@ -205,13 +207,13 @@ export class GBDeployer {
}) })
WaitUntil() WaitUntil()
.interval(1000) .interval(100)
.times(5) .times(5)
.condition(function(cb) { .condition(function (cb) {
logger.info(`Waiting for package deployment...`) logger.info(`Waiting for package deployment...`)
cb(totalPackages == generalPackages.length) cb(totalPackages == generalPackages.length)
}) })
.done(function(result) { .done(function (result) {
if (botPackages.length === 0) { if (botPackages.length === 0) {
logger.info( logger.info(
"The server is running with no bot instances, at least one .gbot file must be deployed." "The server is running with no bot instances, at least one .gbot file must be deployed."

View file

@ -136,7 +136,9 @@ export class GBMinService {
theme: instance.theme, theme: instance.theme,
secret: instance.webchatKey, // TODO: Use token. secret: instance.webchatKey, // TODO: Use token.
speechToken: speechToken, speechToken: speechToken,
conversationId: webchatToken.conversationId conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant,
authenticatorClientID: instance.authenticatorClientID
}) })
) )
} else { } else {
@ -270,97 +272,105 @@ export class GBMinService {
appPackages: any[] appPackages: any[]
) { ) {
return adapter.processActivity(req, res, async context => { return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context)
const dc = min.dialogs.createContext(context, state)
dc.context.activity.locale = "en-US"
const user = min.userState.get(dc.context)
if (!user.loaded) { try {
await min.conversationalService.sendEvent(dc, "loadInstance", { const state = conversationState.get(context)
instanceId: instance.instanceId, const dc = min.dialogs.createContext(context, state)
botId: instance.botId, dc.context.activity.locale = "en-US"
theme: instance.theme, const user = min.userState.get(dc.context)
secret: instance.webchatKey
})
user.loaded = true
user.subjects = []
}
logger.info( if (!user.loaded) {
`[RCV]: ${context.activity.type}, ChannelID: ${ await min.conversationalService.sendEvent(dc, "loadInstance", {
instanceId: instance.instanceId,
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey
})
user.loaded = true
user.subjects = []
}
logger.info(
`[RCV]: ${context.activity.type}, ChannelID: ${
context.activity.channelId context.activity.channelId
}, Name: ${context.activity.name}, Text: ${context.activity.text}.` }, Name: ${context.activity.name}, Text: ${context.activity.text}.`
) )
if ( if (
context.activity.type === "conversationUpdate" && context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0 context.activity.membersAdded.length > 0
) { ) {
let member = context.activity.membersAdded[0] let member = context.activity.membersAdded[0]
if (member.name === "GeneralBots") { if (member.name === "GeneralBots") {
logger.info(`Bot added to conversation, starting chat...`) logger.info(`Bot added to conversation, starting chat...`)
appPackages.forEach(e => { appPackages.forEach(e => {
e.onNewSession(min, dc) e.onNewSession(min, dc)
}) })
// Starts root dialog. // Processes the root dialog.
await dc.begin("/") await dc.begin("/")
} else { } else {
logger.info(`Member added to conversation: ${member.name}`) logger.info(`Member added to conversation: ${member.name}`)
} }
// Processes messages. // Processes messages.
} else if (context.activity.type === "message") { } else if (context.activity.type === "message") {
// Checks for /admin request. // Checks for /admin request.
if (context.activity.text === "admin") { if (context.activity.text === "admin") {
await dc.begin("/admin") await dc.begin("/admin")
// Checks for /menu JSON signature. // Checks for /menu JSON signature.
} else if (context.activity.text.startsWith("{\"title\"")) { } else if (context.activity.text.startsWith("{\"title\"")) {
await dc.begin("/menu", {data:JSON.parse(context.activity.text)}) await dc.begin("/menu", { data: JSON.parse(context.activity.text) })
// Otherwise, continue to the active dialog in the stack. // Otherwise, continue to the active dialog in the stack.
} else { } else {
await dc.continue() await dc.continue()
} }
// Processes events. // Processes events.
} else if (context.activity.type === "event") { } else if (context.activity.type === "event") {
// Empties dialog stack before going to the target. // Empties dialog stack before going to the target.
await dc.endAll() await dc.endAll()
if (context.activity.name === "whoAmI") { if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI") await dc.begin("/whoAmI")
} else if (context.activity.name === "showSubjects") { } else if (context.activity.name === "showSubjects") {
await dc.begin("/menu") await dc.begin("/menu")
} else if (context.activity.name === "giveFeedback") { } else if (context.activity.name === "giveFeedback") {
await dc.begin("/feedback", { await dc.begin("/feedback", {
fromMenu: true fromMenu: true
}) })
} else if (context.activity.name === "showFAQ") { } else if (context.activity.name === "showFAQ") {
await dc.begin("/faq") await dc.begin("/faq")
} else if (context.activity.name === "ask") { } else if (context.activity.name === "ask") {
await dc.begin("/answer", { await dc.begin("/answer", {
query: (context.activity as any).data, query: (context.activity as any).data,
fromFaq: true fromFaq: true
}) })
} else if (context.activity.name === "quality") {
await dc.begin("/quality", { } else if (context.activity.name === "quality") {
// TODO: score: context.activity.data await dc.begin("/quality", { score: (context.activity as any).data })
}) } else if (context.activity.name === "updateToken") {
} else { let token = (context.activity as any).data
await dc.continue() await dc.begin("/adminUpdateToken", { token: token })
} else {
await dc.continue()
}
} }
} catch (error) {
let msg = `Error in main activity: ${error}.`
logger.error(msg)
} }
}) })
} }

View file

@ -55,4 +55,6 @@
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View file

@ -45,6 +45,7 @@ import { SpeechSynthesizer } from "botframework-webchat/CognitiveServices";
import { SynthesisGender } from "botframework-webchat/CognitiveServices"; import { SynthesisGender } from "botframework-webchat/CognitiveServices";
import { Chat } from "botframework-webchat"; import { Chat } from "botframework-webchat";
import GBPowerBIPlayer from "./players/GBPowerBIPlayer.js"; import GBPowerBIPlayer from "./players/GBPowerBIPlayer.js";
import { UserAgentApplication } from "msal";
class GBUIApp extends React.Component { class GBUIApp extends React.Component {
constructor() { constructor() {
@ -58,6 +59,22 @@ class GBUIApp extends React.Component {
}; };
} }
sendToken(token) {
setTimeout(() => {
window.botConnection
.postActivity({
type: "event",
name: "updateToken",
data: token,
locale: "en-us",
textFormat: "plain",
timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" }
})
.subscribe(this.send("success"));
}, 400);
}
send(command) { send(command) {
window.botConnection window.botConnection
.postActivity({ .postActivity({
@ -93,8 +110,11 @@ class GBUIApp extends React.Component {
configureChat() { configureChat() {
var botId = window.location.href.split("/")[3]; var botId = window.location.href.split("/")[3];
if (botId.indexOf('#') != -1) {
botId = botId.split("#")[0];
}
if (!botId) { if (!botId || botId == "") {
botId = "[default]"; botId = "[default]";
} }
@ -102,7 +122,7 @@ class GBUIApp extends React.Component {
.then(res => res.json()) .then(res => res.json())
.then( .then(
result => { result => {
this.setState({instanceClient:result}); this.setState({ instanceClient: result });
this.setupBotConnection(); this.setupBotConnection();
}, },
error => { error => {
@ -114,6 +134,38 @@ class GBUIApp extends React.Component {
); );
} }
authenticate() {
let _this_ = this;
let authority =
"https://login.microsoftonline.com/" +
this.state.instanceClient.authenticatorTenant;
let graphScopes = ["Directory.AccessAsUser.All"];
let userAgentApplication = new UserAgentApplication(
this.state.instanceClient.authenticatorClientID,
authority,
function (errorDesc, token, error, tokenType) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) {
_this_.sendToken(accessToken);
}, function (error) {
console.log(error);
})
}
);
if (!userAgentApplication.isCallback(window.location.hash) && window.parent === window && !window.opener) {
var user = userAgentApplication.getUser();
if (user) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) {
_this_.sendToken(accessToken);
}, function (error) {
console.log(error);
})
}
}
}
setupBotConnection() { setupBotConnection() {
let _this_ = this; let _this_ = this;
window["botchatDebug"] = true; window["botchatDebug"] = true;
@ -124,14 +176,13 @@ class GBUIApp extends React.Component {
botConnection.connectionStatus$.subscribe(connectionStatus => { botConnection.connectionStatus$.subscribe(connectionStatus => {
if (connectionStatus === ConnectionStatus.Online) { if (connectionStatus === ConnectionStatus.Online) {
_this_.setState({ botConnection: botConnection });
botConnection.postActivity({ botConnection.postActivity({
type: "event", type: "event",
value: "startGB", value: "startGB",
from: this.getUser(), from: this.getUser(),
name: "startGB" name: "startGB"
}); });
_this_.setState({ botConnection: botConnection });
} }
}); });
@ -145,6 +196,7 @@ class GBUIApp extends React.Component {
) )
.subscribe(activity => { .subscribe(activity => {
_this_.setState({ instance: activity.value }); _this_.setState({ instance: activity.value });
_this_.authenticate()
}); });
botConnection.activity$ botConnection.activity$
@ -224,7 +276,7 @@ class GBUIApp extends React.Component {
/> />
); );
break; break;
case "login": case "login":
playerComponent = ( playerComponent = (
<GBLoginPlayer <GBLoginPlayer
app={this} app={this}
@ -246,7 +298,7 @@ class GBUIApp extends React.Component {
let speechOptions; let speechOptions;
let chat = <div />; let chat = <div />;
let gbCss =<div />; let gbCss = <div />;
let sideBar = ( let sideBar = (

View file

@ -39,3 +39,4 @@ ReactDOM.render(
<GBUIApp head={document.getElementsByTagName("head")[0]} />, <GBUIApp head={document.getElementsByTagName("head")[0]} />,
document.getElementById("root") document.getElementById("root")
); );

View file

@ -31,68 +31,54 @@
\*****************************************************************************/ \*****************************************************************************/
import React from "react"; import React from "react";
import { Logger, LogLevel } from "msal";
import { UserAgentApplication } from "msal"; import { UserAgentApplication } from "msal";
class GBLoginPlayer extends React.Component { class GBLoginPlayer extends React.Component {
constructor() {
constructor(tenant) {
super(); super();
this.state = { this.state = {
token: "", login: {}
}; };
} }
doLogin(info) {
let logger = new Logger(
login() { (logLevel, message, piiEnabled) => {
console.log(message);
let config = { },
tenant: "pragmatismo.onmicrosoft.com", //"6ecb2a67-15af-4582-ab85-cc65096ce471", { level: LogLevel.Verbose }
signUpSignInPolicy: "b2c_1_susi",
clientID: '47cbaa05-dbb4-46f8-8608-da386c5131f1'}
let authority = "https://login.microsoftonline.com/tfp/" +
config.tenant + "/" +
config.signUpSignInPolicy;
let userAgentApplication = new UserAgentApplication(
config.clientID, authority,
function (errorDesc, token, error, tokenType) {
console.log(token);
}
); );
let authority =
"https://login.microsoftonline.com/" +
this.state.login.authenticatorTenant;
let graphScopes = ["Directory.AccessAsUser.All"]; let graphScopes = ["Directory.AccessAsUser.All"];
userAgentApplication.loginPopup(graphScopes).then(function (idToken) { let userAgentApplication = new UserAgentApplication(
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) { this.state.login.authenticatorClientID,
console.log(accessToken); authority,
function (errorDesc, token, error, tokenType) {
}, function (error) { if (error) {
userAgentApplication.acquireTokenPopup(graphScopes).then(function (accessToken) {
console.log(accessToken);
}, function (error) {
console.log(error); console.log(error);
}); }
}) })
}, function (error) {
console.log(error); userAgentApplication.loginRedirect(graphScopes);
});
} }
play() { play(data) {
this.setState({ login: data });
}
stop() {
this.setState({ login: [] });
} }
render() { render() {
return ( return <button onClick={() => this.doLogin(this.state.login)}>Login</button>;
<button
value="Login"
onClick={this.login}
/>
);
} }
} }

View file

@ -40,7 +40,6 @@ import { BotAdapter } from "botbuilder";
import { Messages } from "../strings"; import { Messages } from "../strings";
import { LuisRecognizer } from "botbuilder-ai"; import { LuisRecognizer } from "botbuilder-ai";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
export class AskDialog extends IGBDialog { export class AskDialog extends IGBDialog {
@ -69,7 +68,7 @@ export class AskDialog extends IGBDialog {
throw new Error(`/answer being called with no args.query text.`); throw new Error(`/answer being called with no args.query text.`);
} }
let locale = dc.context.activity.locale let locale = dc.context.activity.locale;
// Stops any content on projector. // Stops any content on projector.
@ -163,7 +162,7 @@ export class AskDialog extends IGBDialog {
); );
await dc.replace("/ask", { isReturning: true }); await dc.replace("/ask", { isReturning: true });
} else { } else {
if (!(await min.conversationalService.runNLP(dc, min, text))) { if (!(await min.conversationalService.routeNLP(dc, min, text))) {
await dc.context.sendActivity(Messages[locale].did_not_find); await dc.context.sendActivity(Messages[locale].did_not_find);
await dc.replace("/ask", { isReturning: true }); await dc.replace("/ask", { isReturning: true });
} }
@ -181,16 +180,21 @@ export class AskDialog extends IGBDialog {
user.subjects = []; user.subjects = [];
} }
let text = []; let text = [];
if (user.subjects.length > 0) {
text = Messages[locale].which_question;
}
if (args && args.isReturning) { // Three forms of asking.
if (args.firstTime) {
text = Messages[locale].ask_first_time;
} else if (args && args.isReturning) {
text = Messages[locale].anything_else; text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) {
text = Messages[locale].which_question;
} else {
throw new Error("Invalid use of /ask");
} }
if (text.length > 0) { if (text.length > 0) {
await dc.prompt("textPrompt", text[0]); await dc.prompt("textPrompt", text);
} }
}, },
async (dc, value) => { async (dc, value) => {

View file

@ -13,7 +13,9 @@ export const Messages = {
`Vamos pesquisar sobre ${query}... O que deseja saber?`, `Vamos pesquisar sobre ${query}... O que deseja saber?`,
see_faq: see_faq:
"Please take a look at the FAQ I've prepared for you. You can click on them to get the answer.", "Please take a look at the FAQ I've prepared for you. You can click on them to get the answer.",
will_answer_projector:"I'll answer on the projector to a better experience..." will_answer_projector:
"I'll answer on the projector to a better experience...",
ask_first_time: "What are you looking for?"
}, },
"pt-BR": { "pt-BR": {
did_not_find: "Desculpe-me, não encontrei nada a respeito.", did_not_find: "Desculpe-me, não encontrei nada a respeito.",
@ -29,6 +31,8 @@ export const Messages = {
`Let's search about ${query}... What do you want to know?`, `Let's search about ${query}... What do you want to know?`,
see_faq: see_faq:
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", "Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.",
will_answer_projector:"Vou te responder na tela para melhor visualização..." will_answer_projector:
"Vou te responder na tela para melhor visualização...",
ask_first_time: "Sobre como eu poderia ajudar?"
} }
}; };

View file

@ -42,20 +42,22 @@
"botlib": "^0.0.33", "botlib": "^0.0.33",
"chokidar": "^2.0.4", "chokidar": "^2.0.4",
"csv-parse": "^3.0.0", "csv-parse": "^3.0.0",
"dotenv-extended": "^2.2.0", "dotenv-extended": "^2.3.0",
"express": "^4.16.3", "express": "^4.16.3",
"express-promise-router": "^3.0.3", "express-promise-router": "^3.0.3",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"fs-walk": "^0.0.2", "fs-walk": "^0.0.2",
"localize": "^0.4.7", "localize": "^0.4.7",
"marked": "^0.5.0", "marked": "^0.5.0",
"ms": "^2.1.1",
"nexmo": "^2.3.2",
"pragmatismo-io-framework": "^1.0.15", "pragmatismo-io-framework": "^1.0.15",
"reflect-metadata": "^0.1.12", "reflect-metadata": "^0.1.12",
"request-promise-native": "^1.0.5", "request-promise-native": "^1.0.5",
"sequelize": "^4.38.0", "sequelize": "^4.38.1",
"sequelize-typescript": "^0.6.6", "sequelize-typescript": "^0.6.6",
"sqlite3": "^4.0.2", "sqlite3": "^4.0.2",
"swagger-client": "^3.8.19", "swagger-client": "^3.8.21",
"tedious": "^2.6.4", "tedious": "^2.6.4",
"url-join": "^4.0.0", "url-join": "^4.0.0",
"wait-until": "^0.0.2", "wait-until": "^0.0.2",

View file

@ -83,7 +83,6 @@ export class GBServer {
try { try {
logger.info(`Accepting connections on ${port}...`) logger.info(`Accepting connections on ${port}...`)
logger.info(`Starting instances...`)
// Reads basic configuration, initialize minimal services. // Reads basic configuration, initialize minimal services.
@ -93,6 +92,7 @@ export class GBServer {
// Boot a bot package if any. // Boot a bot package if any.
logger.info(`Starting instances...`)
let deployer = new GBDeployer(core, new GBImporter(core)) let deployer = new GBDeployer(core, new GBImporter(core))
// Build a minimal bot instance for each .gbot deployment. // Build a minimal bot instance for each .gbot deployment.