diff --git a/.eslintrc.js b/.eslintrc.js index 0b3aa22..d851acf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { browser: true, 'jest/globals': true, }, + ignorePatterns: ['node_modules/**'], settings: { react: { version: 'detect', diff --git a/.gitignore b/.gitignore index cda40ce..b2be3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /node_modules /.pnp .pnp.js - +.vscode # testing /coverage diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0b4fbaf..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,19 +0,0 @@ -/* -👋 Hi! This file was autogenerated by tslint-to-eslint-config. -https://github.com/typescript-eslint/tslint-to-eslint-config - -It represents the closest reasonable ESLint configuration to this -project's original TSLint configuration. - -We recommend eventually switching this configuration to extend from -the recommended rulesets in typescript-eslint. -https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md - -Happy linting! 💖 -*/ -{ - "cSpell.words": [ - "botcommander", - "esnext" - ] -} diff --git a/README.md b/README.md index 660302a..34d17b4 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,153 @@ -![Formulated Logo](https://www.formulatedautomation.com/wp-content/uploads/2020/07/Subtract-660x20-1.svg) +![FA Github Header](https://user-images.githubusercontent.com/2868/98735818-fabe8a80-2371-11eb-884a-e555e31aa348.png) +# Bot Commander -**Formulated Autoamation RPA Resources** +[![FormulatedAutomation](https://circleci.com/gh/FormulatedAutomation/Profiler.svg?style=shield)](https://app.circleci.com/pipelines/github/FormulatedAutomation/Profiler) +![image](https://user-images.githubusercontent.com/2868/95122864-20c39000-071f-11eb-86c8-63820b013ae4.png) -- [/r/OpenSourceRPA](https://reddit.com/r/OpenSourceRPA) -- [OpenSource RPA LinkedIn - Group](https://www.linkedin.com/groups/12366622/) -- [FormulatedAutomation's YouTube - Screencasts](https://www.youtube.com/channel/UC_IMgIFlNBG94Vm8tNCNeUQ) -- [Formulated Automation Podcast](https://www.formulatedautomation.com/category/podcast/) +### A human-friendly interface for running bots. +Bot Commander talks to vendor APIs directly to provide an environment for business-stakeholders to work with automations. -# FormulatedAutomation-Profiler +Bot Commander is a [nextjs](https://nextjs.org/) web application that speaks directly to RPA vendor APIs for you. It offers an extensible starting point if you need to trigger bots running and report on their status. -[![FormulatedAutomation](https://circleci.com/gh/FormulatedAutomation/Profiler.svg?style=shield)](https://app.circleci.com/pipelines/github/FormulatedAutomation/Profiler) -[![PyPI version](https://badge.fury.io/py/fa-profiler.svg)](https://badge.fury.io/py/fa-profiler) + + +![https://formulated.s3.us-east-2.amazonaws.com/processes-3.gif](https://formulated.s3.us-east-2.amazonaws.com/processes-3.gif) -![image](https://user-images.githubusercontent.com/2868/86496363-2473ff00-bd4b-11ea-868a-ee07a2ace9d9.png) +Currently, UiPath and Robocorp bots are supported but more platforms can be implemented via a system of pluggable backends. Contribution guidelines are forthcoming but pull requests are welcome. -### Introduction +This is experimental software and will be undergoing rapid change -This project allows organizations to quickly create interfaces for -their bots for end users. +# Get Started -⚠️ This project is currently a work in process and should not be used in -production environments. ⚠️ +Before getting started, you will need API Access to your bot provider. This guide assumes that you have access to your vendor and have bots that you would like to run. -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +### Download the Project -## Getting Started +```bash +git clone git@github.com:FormulatedAutomation/BotCommander.git +npm install +``` -### Installation +## Configuration -//TODO: Installation steps +### Connecting UiPath -### Configuration +To connect your UiPath bot, you need both the account API credentials as well as the bots basic information. -//TODO: Configuration steps +API credentials will be listed in under 'sources' in `config/api.js`, while the bot will be under `bots` -### Running the server +An example of a UiPath source config would be: -//TODO: How to run the server +```jsx +{ + uipath: { + platformHostname: 'platform.uipath.com', + path: 'organization/myServiceInstanceLogicalName', + refreshToken: process.env.UIPATH_REFRESH_TOKEN, + serviceInstanceLogicalName: 'myServiceInstanceLogicalName', + clientId: 'abc123456', + }, +``` + +Once you've defined a source, you can then define the bots which will use those source credentials to interact with the API. + +The definiation of a UiPath bot looks like this: + +```js + { + id: 'Turkish.Lira.to.USD', + name: 'Turkish Lira Conversion Bot', + description: 'Converts Turkish Lira to USD', + source: 'uipath', + type: 'uipath', + acl: { + groups: ['admins', 'users'], + }, + }, +``` + +For more details about where to find these keys, [click here](uipath_credentials_how_to.md). + +### Connecting Robocloud + +To connect your Robocloud bot, you need three pieces of information about your bot: + +- Workspace ID +- Process ID +- Process Secret + +This information can be found in the Robocloud + +- [Activate the Process API](https://robocorp.com/docs/product-manuals/robocorp-cloud/robocorp-cloud-process-api) in Robocloud +- Add your Process Secret and bot values to `config/api.js` within the `bots` list. + +Here is an example entry: + +```jsx +{ + id: 'Robocloud.Demo', + name: 'My Robot', + description: 'My Special Robot That Helps Me Out', + secret: '****PROCESS-SECRET****', + workspaceId: '****WORKSPACE-ID****', + processId: '****PROCESS-ID****', + type: 'robocloud', + acl: { + groups: ['admins', 'users'], + }, + } +``` -First, run the development server: +## Access Control List + +TBD high-level explanation of how ACL works. + +## Authorization + +Bot Commander uses [next-auth](https://next-auth.js.org/) and have included an [Auth0](https://auth0.com/) provider. An example of the auth config file found at `config/auth.js` + +```jsx +// config/auth.js + +export default [ + Providers.Auth0({ + domain: '****AUTH0-DOMAIN****', + clientId: '****CLIENT-ID****', + clientSecret: '****CLIENT-SECRET****', + }), + // ...add more providers here +] +``` + +## Environment Variables + +Create a file named `.env` in the root directory of the project with environment variables you would like the application to use. You should also be able to use actual environment variables as well. + +TBD - Required Variables to Add + +## Running + +Since the app is a nextjs application you can find plentiful [resources](https://nextjs.org/docs) on how to get setup. To test out your instance just run the following to launch the dev server. ```bash -SECRET=123456 npm run dev -# or -SECRET=123456 yarn dev +# be sure to `npm install` before this +npm run dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. \ No newline at end of file +## Docker Guide + +Bot Commander works well with Docker as a self-contained nodejs application. + + +**Formulated Automation RPA Resources** + + +- [/r/OpenSourceRPA](https://reddit.com/r/OpenSourceRPA) +- [OpenSource RPA LinkedIn + Group](https://www.linkedin.com/groups/12366622/) +- [FormulatedAutomation's YouTube + Screencasts](https://www.youtube.com/channel/UC_IMgIFlNBG94Vm8tNCNeUQ) +- [Formulated Automation Podcast](https://www.formulatedautomation.com/category/podcast/) diff --git a/components/BotRunForm.js b/components/BotRunForm.js index 22337e9..cd5faae 100644 --- a/components/BotRunForm.js +++ b/components/BotRunForm.js @@ -12,7 +12,7 @@ const BotRunForm = ({ botInfo, setInputArgs, handleSubmit, loading }) => {
- diff --git a/components/InputArgsFields.tsx b/components/InputArgsFields.tsx index efcaab0..a4131d1 100644 --- a/components/InputArgsFields.tsx +++ b/components/InputArgsFields.tsx @@ -24,11 +24,13 @@ const InputArgsFields: FunctionComponent<{ inputArgs: InputArgs[], inputArgsChan
{Object.values(inputArgs).map(inputArg => (
- +
-
diff --git a/components/InputOutputArgs.tsx b/components/InputOutputArgs.tsx new file mode 100644 index 0000000..a726729 --- /dev/null +++ b/components/InputOutputArgs.tsx @@ -0,0 +1,52 @@ +import React, { FunctionComponent } from 'react' +import PropTypes from 'prop-types' + +interface InputArgs { + [key: string]: any +} + +interface OutputArgs { + [key: string]: any +} + +const KeyList = ({ obj, keyPrefix }: { obj: object, keyPrefix: string }) => { + return
+ { + Object.keys(obj).map((key, idx) => { + return ( +
+
+ {key} +
+
+ {obj[key]} +
+
+ ) + }) + } + +
+} + +const InputOutputArgs: FunctionComponent<{ inputArgs: InputArgs, outputArgs: OutputArgs }> = ({ inputArgs, outputArgs }) => { + return ( +
+
+

+ Run Results +

+
+
+ + +
+
) +} + +InputOutputArgs.propTypes = { + inputArgs: PropTypes.object, + outputArgs: PropTypes.object, +} + +export default InputOutputArgs diff --git a/config/api.js b/config/api.js index bfa9384..ce1f3e4 100644 --- a/config/api.js +++ b/config/api.js @@ -5,67 +5,75 @@ export const acl = { 'm@mdp.im', 'percival@gmail.com', 'brent@brentsanders.io', - ] + ], }, users: { emails: [ '/^[A-Z0-9._\\-+\\%]+@formulatedautomation.com$/i', - '*@fultonworks.co', - ] + ], }, anotherGroup: { emails: [ - 'foo@foo.com' - ] - } - } + 'foo@foo.com', + ], + }, + }, } -export const bots = +export const bots = [ { id: 'Turkish.Lira.to.USD', - name: "Turkish Lira Conversion Bot", - description: "Converts Turkish Lira to USD", + name: 'Turkish Lira Conversion Bot', + description: 'Converts Turkish Lira to USD', source: 'uipath', type: 'uipath', acl: { - groups: ['admins', 'users'] + groups: ['admins', 'users'], }, }, { - id: 'Robocloud.Demo', - name: "Code Scraper", - description: "Code Violations Scraper", - secret: process.env.CODE_VIOLATION_BOT_SECRET, - workspaceId: "971988d2-33d9-4cdf-acd4-2d7f7b64814e", - processId: "0acf110e-5160-43f4-a82c-9c1b5a2472dc", + id: 'Stock.History', + name: 'Stock History Bot', + description: 'Stock History Downloader', + secret: process.env.STOCK_HISTORY_SECRET, + workspaceId: 'da69252b-22dd-4852-a316-5074e316ace7', + processId: '136e1ea8-e6ae-4da6-b3d3-cfc4227fa3d9', type: 'robocloud', + arguments: { + input: [ + { + name: 'ticker', + displayName: 'Ticker', + type: 'string', + }, + ], + }, acl: { - groups: ['admins', 'users'] + groups: ['admins', 'users'], }, }, { - id: 'ACL.Demo.Bot', - name: "ACL Demo Bot", - description: "A bot that should be available to no one", - secret: "abc123", - workspaceId: "12345332-33d9-4cdf-acd4-222222222222", - processId: "12323456-5160-43f4-a82c-222222222222", + id: 'No.Access.Bot', + name: 'ACL Demo Bot', + description: 'A bot that should be available to no one - For test purposes', + secret: 'abc123', + workspaceId: '12345332-33d9-4cdf-acd4-222222222222', + processId: '12323456-5160-43f4-a82c-222222222222', type: 'robocloud', acl: { - groups: ['anotherGroup'] + groups: ['anotherGroup'], }, }, ] -export const sources = +export const sources = { uipath: { - platformHostname: "platform.uipath.com", - path: "trailingspacell/TrailingSpavyun205999", + platformHostname: 'platform.uipath.com', + path: 'trailingspacell/TrailingSpavyun205999', refreshToken: process.env.UIPATH_REFRESH_TOKEN, - serviceInstanceLogicalName: "TrailingSpavyun205999", - clientId: "8DEv1AMNXczW3y4U15LL3jYf62jK93n5", + serviceInstanceLogicalName: 'TrailingSpavyun205999', + clientId: '8DEv1AMNXczW3y4U15LL3jYf62jK93n5', }, -} \ No newline at end of file +} diff --git a/config/auth.js b/config/auth.js index 6498af5..47e95aa 100644 --- a/config/auth.js +++ b/config/auth.js @@ -4,7 +4,7 @@ export default [ Providers.Auth0({ domain: 'formulated-dev1.us.auth0.com', clientId: 'dD27q7zaJCTUtVfoNNY6fIHZStO3JpvA', - clientSecret: 'lJsUtQPTjJmfpHsAmmtQcLz_bMOxxV4ED0nAGq8rVHQRnmItNQpIx3g1x4gLDd3R', + clientSecret: process.env.AUTH0_SECRET, }), // ...add more providers here ] diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js index 48d6966..9ec4261 100644 --- a/config/jest/cssTransform.js +++ b/config/jest/cssTransform.js @@ -1,8 +1,8 @@ module.exports = { - process() { + process () { return 'module.exports = {};' }, - getCacheKey() { + getCacheKey () { return 'cssTransform' }, -} \ No newline at end of file +} diff --git a/config/secret.ts b/config/secret.ts index 2286710..ac762eb 100644 --- a/config/secret.ts +++ b/config/secret.ts @@ -1,15 +1,24 @@ // Secret for JWT and Session storage // Will automatically be generated if not found at config/secret.txt import fs from 'fs' import path from 'path' -import {promisify} from 'util' +import { promisify } from 'util' const secretPath = path.join(process.cwd(), '/config/secret.txt') const MIN_SECRET_LEN = 25 // Even in base64, this would be less than 128 bits of entropy -const fsExists = promisify(fs.exists) +const fsExists = (path) => { + return new Promise((resolve) => { + fs.access(path, fs.constants.F_OK, (err) => { + if (err) { + return resolve(false) + } + return resolve(true) + }) + }) +} const fsReadFile = promisify(fs.readFile) -export async function get(): Promise { +export async function get (): Promise { let secret = null if (process.env.SECRET) { secret = process.env.SECRET @@ -18,13 +27,13 @@ export async function get(): Promise { } if (!secret) { console.log(`Secret not found, please either set SECRET or create a file at '${secretPath}' containing the secret key`) - console.log(`Exiting...`) + console.log('Exiting...') process.exit(1) } if (secret && secret.length < MIN_SECRET_LEN) { console.log(`Secret is not secure, please set a secret > ${MIN_SECRET_LEN} characters in length.`) - console.log(`Exiting...`) + console.log('Exiting...') process.exit(1) } return secret -} \ No newline at end of file +} diff --git a/constants/menu.js b/constants/menu.js index b5d6d89..187fc49 100644 --- a/constants/menu.js +++ b/constants/menu.js @@ -1,8 +1,8 @@ const MENU = [ { - label: "Bots", - link: '/bots' + label: 'Bots', + link: '/bots', }, -]; +] -export default MENU; +export default MENU diff --git a/demo.js b/demo.js index 5a9130a..a9eccdf 100644 --- a/demo.js +++ b/demo.js @@ -1,31 +1,31 @@ -const config = require('./config/sources')['uipath'] +const config = require('./config/sources').uipath const { OrchestratorApi } = require('uipath-orchestrator-api-node') -const oc = new OrchestratorApi ( config ) - +const oc = new OrchestratorApi(config) + const main = async () => { - const token = await oc.authenticate() + await oc.authenticate() const releases = await oc.release.findAll() console.log(releases) const release = releases.find((r) => r.ProcessKey === 'Turkish.Lira.to.USD') - console.log("Starting Process", release.ProcessKey) + console.log('Starting Process', release.ProcessKey) const inputArgs = { inputLira: 3500, } const result = await oc.job._startJobs({ - startInfo: { - ReleaseKey: release.Key, - RobotIds: [], - JobsCount: 1, - Strategy: 'JobsCount', - InputArguments: JSON.stringify(inputArgs), - } - }) + startInfo: { + ReleaseKey: release.Key, + RobotIds: [], + JobsCount: 1, + Strategy: 'JobsCount', + InputArguments: JSON.stringify(inputArgs), + }, + }) console.log(result.value) } - + if (!module.parent) { main() -} \ No newline at end of file +} diff --git a/jest.config.js b/jest.config.js index e90fad6..3863e5c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,4 +17,4 @@ module.exports = { moduleNameMapper: { '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', }, -} \ No newline at end of file +} diff --git a/lib/__tests__/acl_test.ts b/lib/__tests__/acl_test.ts index 840f911..145a390 100644 --- a/lib/__tests__/acl_test.ts +++ b/lib/__tests__/acl_test.ts @@ -45,6 +45,6 @@ it('should fail against and unauthorized email with regex', () => { it('should list only the bots available to a user', () => { const aclObj = new ACL(acl, botInstances) - expect(aclObj.isAllowed(getEmailToken('m@mdp.im'), 'ACL.Demo.Bot')).toEqual(false) + expect(aclObj.isAllowed(getEmailToken('m@mdp.im'), 'No.Access.Bot')).toEqual(false) expect(aclObj.listBots(getEmailToken('m@mdp.im')).length).toEqual(2) }) diff --git a/lib/acl.ts b/lib/acl.ts index 9067a81..7617203 100644 --- a/lib/acl.ts +++ b/lib/acl.ts @@ -40,7 +40,10 @@ export class ACL { } // Get back a list of bots that a user is allowed to use - listBots (token: Token): Bot[] { + listBots (token: Token | null): Bot[] { + if (!token) { + return [] + } const allowedBots = [] for (const bot of this.bots) { const botAclGroups = bot.botConfig.acl.groups @@ -64,6 +67,7 @@ export class ACL { // Returns a Boolean noting is a user is able to access a process isAllowed (token: Token, botId: string): boolean { const bot = this.bots.find((b) => botId === b.id) + if (!bot) return false const botAclGroups = bot.botConfig.acl.groups for (const group of botAclGroups) { const acl = this.acl.groups[group] diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..d91d7d9 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,3 @@ +export function firstArrayElement (arrOrString: string[] | string): string { + return Array.isArray(arrOrString) ? arrOrString[0] : arrOrString +} diff --git a/package.json b/package.json index d97e855..d4a78b8 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "build": "next build", "start": "next start -p $PORT", "test": "jest", - "lint": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\"", + "lint": "eslint .", + "lint-fix": "eslint . --fix", "heroku-postbuild": "npm run build" }, "dependencies": { "@fullhuman/postcss-purgecss": "^2.1.0", "@tailwindcss/ui": "^0.1.3", + "@types/test-listen": "^1.1.0", "@zeit/next-css": "^1.0.1", "classnames": "^2.2.6", "config": "^3.3.1", @@ -27,6 +29,7 @@ "react-dom": "16.13.1", "react-json-view": "^1.19.1", "tailwindcss": "^1.2.0", + "test-listen": "^1.1.0", "tsx": "^1.0.0", "uipath-orchestrator": "^1.1.3", "winston": "^3.3.3" diff --git a/pages/api/botcommander/__tests__/ping_test.ts b/pages/api/botcommander/__tests__/ping_test.ts index 3fcb132..9778ffc 100644 --- a/pages/api/botcommander/__tests__/ping_test.ts +++ b/pages/api/botcommander/__tests__/ping_test.ts @@ -1,6 +1,19 @@ -import { handler } from '../ping' +import http from 'http' +import fetch from 'node-fetch' +import listen from 'test-listen' import { createMocks } from 'node-mocks-http' -import { loggedInContext } from '../../../../tests/api_test_helper' +import { apiResolver } from 'next/dist/next-server/server/api-utils' + +import { authedCookie, loggedInContext } from '../../../../tests/api_test_helper' +import Ping, { handler } from '../ping' + +const servers = [] + +afterAll(() => { + for (const server of servers) { + server.close() + } +}) it('Should return the authenticated users token', async () => { const { req, res } = createMocks({ @@ -11,3 +24,37 @@ it('Should return the authenticated users token', async () => { const data = res._getJSONData() expect(data.token.email).toEqual('m@mdp.im') }) + +describe('/api/botcommander/ping', () => { + test('responds 401 to unauthed GET', async () => { + expect.assertions(1) + const requestHandler = (req, res) => { + return apiResolver(req, res, undefined, Ping, null, false) + } + const server = http.createServer(requestHandler) + servers.push(server) + const url = await listen(server) + const response = await fetch(url) + expect(response.status).toBe(401) + }) + + test('responds 200 to authed GET', async () => { + expect.assertions(3) + const requestHandler = (req, res) => { + return apiResolver(req, res, undefined, Ping, null, false) + } + const server = http.createServer(requestHandler) + servers.push(server) + const url = await listen(server) + const opts = { + headers: { + cookie: await authedCookie('m@mdp.im'), + }, + } + const response = await fetch(url, opts) + expect(response.status).toBe(200) + const json = await response.json() + expect(json.bots.length).toEqual(2) + expect(json.token.email).toEqual('m@mdp.im') + }) +}) diff --git a/pages/api/botcommander/bots/[id].ts b/pages/api/botcommander/bots/[id].ts index cdf7244..fd3dc87 100644 --- a/pages/api/botcommander/bots/[id].ts +++ b/pages/api/botcommander/bots/[id].ts @@ -10,9 +10,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse, ctx: BotComman } } if (bot) { - const botJSON: any = bot.definition() - botJSON.properties = await bot.properties() - res.json(botJSON) + res.json(await bot.toJSON()) return } res.statusCode = 404 diff --git a/pages/api/botcommander/jobs/[botId]/[runId].ts b/pages/api/botcommander/jobs/[botId]/[runId].ts index 24c4357..189e4f7 100644 --- a/pages/api/botcommander/jobs/[botId]/[runId].ts +++ b/pages/api/botcommander/jobs/[botId]/[runId].ts @@ -7,7 +7,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse, context: BotCo const botId: string = Array.isArray(req.query.botId) ? req.query.botId[0] : req.query.botId const bot = bots.find((b) => b.id === botId) const job = bot.getJob(id) - const info = await job.properties() + const info = await job.toJSON() res.statusCode = 200 res.json(info) } diff --git a/pages/api/botcommander/ping.ts b/pages/api/botcommander/ping.ts index 3640052..2c1d674 100644 --- a/pages/api/botcommander/ping.ts +++ b/pages/api/botcommander/ping.ts @@ -4,8 +4,10 @@ import { BotCommanderContext, ensureLoggedIn } from '../../../server/middleware/ export const handler = (req: NextApiRequest, res: NextApiResponse, context: BotCommanderContext) => { const token = context.token + const bots = context.bots.map(bot => ({ id: bot.id })) + console.log(bots) res.statusCode = 200 - res.json({ token }) + res.json({ token, bots: bots }) } export default ensureLoggedIn(handler) diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx index 16eb4d7..bdf3f94 100644 --- a/pages/auth/signin.tsx +++ b/pages/auth/signin.tsx @@ -1,9 +1,24 @@ -import React from 'react' -import { providers as getProviders, signIn } from 'next-auth/client' -import PublicLayout from '../../components/PublicLayout' +import React, { useEffect } from 'react' +import { providers as getProviders, signIn, useSession } from 'next-auth/client' import { Providers } from 'next-auth/providers' -export default function SignIn ({ providers }: {providers: Providers}) { +import PublicLayout from '../../components/PublicLayout' +import Router, { useRouter } from 'next/router' +import { firstArrayElement } from '../../lib/utils' + +export default function SignIn ({ providers }: {providers: Providers }) { + const router = useRouter() + const [session] = useSession() + + useEffect(() => { + if (session) { + if (router.query.callbackUrl) { + Router.push(firstArrayElement(router.query.callbackUrl)) + } + Router.push('/bots') + } + }, [session]) + return (
diff --git a/pages/bot/[id].tsx b/pages/bot/[id].tsx index d8f36bf..4982da3 100644 --- a/pages/bot/[id].tsx +++ b/pages/bot/[id].tsx @@ -1,15 +1,15 @@ import React, { useState } from 'react' import Layout from '../../components/Layout' -import { AppProps } from 'next/dist/next-server/lib/router/router' import { GetServerSideProps, GetServerSidePropsContext } from 'next' import dynamic from 'next/dynamic' import ErrorAlert from '../../components/ErrorAlert' import BotRunForm from '../../components/BotRunForm' import BotRunButton from '../../components/BotRunButton' import Router from 'next/router' +import { BotObject } from '../../server/models/types' const DynamicReactJson = dynamic(import('react-json-view'), { ssr: false }) -const BotView = ({ botInfo }: AppProps) => { +const BotView = ({ botInfo }: {botInfo: BotObject}) => { const [loading, setLoading] = useState(false) const [inputArgs, setInputArgs] = useState({}) const [runError, setRunError] = useState(null) @@ -43,16 +43,13 @@ const BotView = ({ botInfo }: AppProps) => {
- {botInfo.type === 'uipath' ? ( + {botInfo.arguments && botInfo.arguments.input ? ( - ) : (false)} + ) :
} - {botInfo.type === 'robocloud' ? ( -
- ) : (false)}
@@ -109,10 +106,10 @@ const BotView = ({ botInfo }: AppProps) => {
- Access + Arguments
- +
diff --git a/pages/index.js b/pages/index.js index 04cab38..50c95f0 100644 --- a/pages/index.js +++ b/pages/index.js @@ -4,11 +4,9 @@ import { useSession } from 'next-auth/client' import PublicLayout from '../components/PublicLayout' export default function Page () { - const [session, loading] = useSession() + const [session] = useSession() useEffect(() => { - console.log(session) - console.log(loading) if (session) { Router.push('/bots') } else if (session === null) { diff --git a/pages/job/[botId]/[id].tsx b/pages/job/[botId]/[id].tsx index 0cbc481..51c493c 100644 --- a/pages/job/[botId]/[id].tsx +++ b/pages/job/[botId]/[id].tsx @@ -6,10 +6,11 @@ import Layout from '../../../components/Layout' import AttachmentTable from '../../../components/AttachmentTable' import Link from 'next/link' import classNames from 'classnames' +import InputOutputArgs from '../../../components/InputOutputArgs' const DynamicReactJson = dynamic(import('react-json-view'), { ssr: false }) -const BotView = ({ jobInfo, botInfo }: AppProps) => { +const BotView = ({ jobInfo, botInfo, hostUrl }: AppProps) => { const [loading, setLoading] = useState(false) const [bot, setBot] = useState(botInfo) const [job, setJob] = useState(jobInfo) @@ -17,9 +18,8 @@ const BotView = ({ jobInfo, botInfo }: AppProps) => { const refresh = async (botId, jobId) => { setLoading(true) - const hostname = process.env.HOST_URL || 'http://localhost:3000' - const res = await fetch(`${hostname}/api/botcommand/jobs/${botId}/${jobId}`) - const botRes = await fetch(`${hostname}/api/botcommand/bots/${botId}`) + const res = await fetch(`${hostUrl}/api/botcommander/jobs/${botId}/${jobId}`) + const botRes = await fetch(`${hostUrl}/api/botcommander/bots/${botId}`) const refreshedBotInfo = await botRes.json() const refreshedJobInfo = await res.json() setBot(refreshedBotInfo) @@ -69,8 +69,8 @@ const BotView = ({ jobInfo, botInfo }: AppProps) => { {job.artifacts &&
} - {job.OutputArguments &&
{job.OutputArguments}
} - + {job.arguments && job.arguments.output && } +