diff options
author | vellichora <fangrui_tong@brown.edu> | 2020-02-29 13:52:53 -0500 |
---|---|---|
committer | vellichora <fangrui_tong@brown.edu> | 2020-02-29 13:52:53 -0500 |
commit | 14a2f361c73e468547d48792478e5e7e7e78f4d6 (patch) | |
tree | 533b2035127df71efcebba2a5634e8cada5f03ee | |
parent | 177b5b587ccf53ce0354d2bb2c527bde5785230b (diff) |
filter UI
-rw-r--r-- | package-lock.json | 41 | ||||
-rw-r--r-- | src/client/views/SearchDocBox.tsx | 34 | ||||
-rw-r--r-- | src/client/views/search/CheckBox.tsx | 146 | ||||
-rw-r--r-- | src/client/views/search/SearchBox.tsx | 47 | ||||
-rw-r--r-- | src/server/ApiManagers/SessionManager.ts | 20 | ||||
-rw-r--r-- | src/server/DashSession/DashSessionAgent.ts | 446 | ||||
-rw-r--r-- | src/server/index.ts | 4 |
7 files changed, 384 insertions, 354 deletions
diff --git a/package-lock.json b/package-lock.json index f6b1e80da..e60950308 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5600,8 +5600,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -5619,13 +5618,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5638,18 +5635,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -5752,8 +5746,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -5763,7 +5756,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5776,20 +5768,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5806,7 +5795,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5887,8 +5875,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -5898,7 +5885,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5974,8 +5960,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -6005,7 +5990,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6023,7 +6007,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6062,13 +6045,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx index 5f308ff71..5a76a882f 100644 --- a/src/client/views/SearchDocBox.tsx +++ b/src/client/views/SearchDocBox.tsx @@ -39,6 +39,8 @@ export const keyPlaceholder = "Query"; @observer export class SearchDocBox extends React.Component<FieldViewProps> { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchDocBox, fieldKey); } + // @observable private _display: boolean = false; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @@ -72,7 +74,7 @@ export class SearchDocBox extends React.Component<FieldViewProps> { console.log("didit" ); this.query = StrCast(this.props.Document.searchText); - this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query})); + this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query })); }); if (this.inputRef.current) { @@ -89,16 +91,16 @@ export class SearchDocBox extends React.Component<FieldViewProps> { @action updateKey = async (newKey: string) => { this.query = newKey; - if (newKey.length >1){ - let newdocs= await this.getAllResults(this.query) + if (newKey.length > 1) { + let newdocs = await this.getAllResults(this.query); let things = newdocs.docs console.log(things); console.log(this.content); runInAction(() => { - this.content= Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }); - }); - console.log(this.content); - } + this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }); + }); + console.log(this.content); + } //this.keyRef.current && this.keyRef.current.setIsFocused(false); @@ -161,11 +163,11 @@ export class SearchDocBox extends React.Component<FieldViewProps> { enter = async (e: React.KeyboardEvent) => { console.log(e.key); if (e.key === "Enter") { - let newdocs= await this.getAllResults(this.query) + let newdocs = await this.getAllResults(this.query) let things = newdocs.docs console.log(things); - this.content=Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` }); - + this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` }); + } } @@ -407,9 +409,9 @@ export class SearchDocBox extends React.Component<FieldViewProps> { return ( <div style={{ pointerEvents: "all" }}> <ContentFittingDocumentView {...this.props} - Document={this.content} - getTransform={this.props.ScreenToLocalTransform}> - s</ContentFittingDocumentView> + Document={this.content} + getTransform={this.props.ScreenToLocalTransform}> + s</ContentFittingDocumentView> <div style={{ position: "absolute", @@ -421,12 +423,12 @@ export class SearchDocBox extends React.Component<FieldViewProps> { opacity: 1, transition: "0.4s opacity ease", zIndex: 99, - top:0, + top: 0, }} title={"Add Metadata"} - onClick={action(() => {this.editingMetadata = !this.editingMetadata })} + onClick={action(() => { this.editingMetadata = !this.editingMetadata })} /> - <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease",position:"absolute",top:0,left:0, height:20, width:"-webkit-fill-available" }}> + <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease", position: "absolute", top: 0, left: 0, height: 20, width: "-webkit-fill-available" }}> <EditableView contents={this.query} SetValue={this.updateKey} diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx index a9d48219a..8c97d5dbc 100644 --- a/src/client/views/search/CheckBox.tsx +++ b/src/client/views/search/CheckBox.tsx @@ -17,8 +17,8 @@ interface CheckBoxProps { export class CheckBox extends React.Component<CheckBoxProps>{ // true = checked, false = unchecked @observable private _status: boolean; - @observable private uncheckTimeline: anime.AnimeTimelineInstance; - @observable private checkTimeline: anime.AnimeTimelineInstance; + // @observable private uncheckTimeline: anime.AnimeTimelineInstance; + // @observable private checkTimeline: anime.AnimeTimelineInstance; @observable private checkRef: any; @observable private _resetReaction?: IReactionDisposer; @@ -28,87 +28,87 @@ export class CheckBox extends React.Component<CheckBoxProps>{ this._status = this.props.originalStatus; this.checkRef = React.createRef(); - this.checkTimeline = anime.timeline({ - loop: false, - autoplay: false, - direction: "normal", - }); this.uncheckTimeline = anime.timeline({ - loop: false, - autoplay: false, - direction: "normal", - }); + // this.checkTimeline = anime.timeline({ + // loop: false, + // autoplay: false, + // direction: "normal", + // }); this.uncheckTimeline = anime.timeline({ + // loop: false, + // autoplay: false, + // direction: "normal", + // }); } - componentDidMount = () => { - this.uncheckTimeline.add({ - targets: this.checkRef.current, - easing: "easeInOutQuad", - duration: 500, - opacity: 0, - }); - this.checkTimeline.add({ - targets: this.checkRef.current, - easing: "easeInOutQuad", - duration: 500, - strokeDashoffset: [anime.setDashoffset, 0], - opacity: 1 - }); + // componentDidMount = () => { + // this.uncheckTimeline.add({ + // targets: this.checkRef.current, + // easing: "easeInOutQuad", + // duration: 500, + // opacity: 0, + // }); + // this.checkTimeline.add({ + // targets: this.checkRef.current, + // easing: "easeInOutQuad", + // duration: 500, + // strokeDashoffset: [anime.setDashoffset, 0], + // opacity: 1 + // }); - if (this.props.originalStatus) { - this.checkTimeline.play(); - this.checkTimeline.restart(); - } + // if (this.props.originalStatus) { + // this.checkTimeline.play(); + // this.checkTimeline.restart(); + // } - this._resetReaction = reaction( - () => this.props.parent._resetBoolean, - () => { - if (this.props.parent._resetBoolean) { - runInAction(() => { - this.reset(); - this.props.parent._resetCounter++; - if (this.props.parent._resetCounter === this.props.numCount) { - this.props.parent._resetCounter = 0; - this.props.parent._resetBoolean = false; - } - }); - } - }, - ); - } + // this._resetReaction = reaction( + // () => this.props.parent._resetBoolean, + // () => { + // if (this.props.parent._resetBoolean) { + // runInAction(() => { + // this.reset(); + // this.props.parent._resetCounter++; + // if (this.props.parent._resetCounter === this.props.numCount) { + // this.props.parent._resetCounter = 0; + // this.props.parent._resetBoolean = false; + // } + // }); + // } + // }, + // ); + // } - @action.bound - reset() { - if (this.props.default) { - if (!this._status) { - this._status = true; - this.checkTimeline.play(); - this.checkTimeline.restart(); - } - } - else { - if (this._status) { - this._status = false; - this.uncheckTimeline.play(); - this.uncheckTimeline.restart(); - } - } + // @action.bound + // reset() { + // if (this.props.default) { + // if (!this._status) { + // this._status = true; + // this.checkTimeline.play(); + // this.checkTimeline.restart(); + // } + // } + // else { + // if (this._status) { + // this._status = false; + // this.uncheckTimeline.play(); + // this.uncheckTimeline.restart(); + // } + // } - this.props.updateStatus(this.props.default); - } + // this.props.updateStatus(this.props.default); + // } @action.bound onClick = () => { - if (this._status) { - this.uncheckTimeline.play(); - this.uncheckTimeline.restart(); - } - else { - this.checkTimeline.play(); - this.checkTimeline.restart(); + // if (this._status) { + // this.uncheckTimeline.play(); + // this.uncheckTimeline.restart(); + // } + // else { + // this.checkTimeline.play(); + // this.checkTimeline.restart(); - } - this._status = !this._status; - this.props.updateStatus(this._status); + // } + // this._status = !this._status; + // this.props.updateStatus(this._status); } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 9af8829fa..02b41bc70 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -17,6 +17,7 @@ import "./FilterBox.scss"; import "./SearchBox.scss"; import { SearchItem } from './SearchItem'; import { IconBar } from './IconBar'; +import { FieldFilters } from './FieldFilters'; library.add(faTimes); @@ -338,6 +339,9 @@ export class SearchBox extends React.Component { @computed get resultHeight() { return this._numTotalResults * 70; } + @observable private _filterOpen: boolean = false; + + render() { return ( <div className="searchBox-container" onPointerDown={e => { e.stopPropagation(); e.preventDefault(); }}> @@ -353,6 +357,49 @@ export class SearchBox extends React.Component { <div className="searchBox-quickFilter" onPointerDown={this.openSearch}> <div className="filter-panel"><IconBar /></div> </div> + <div> + <div style={{ display: "flex", flexDirection: "row-reverse" }}> + <SearchBox /> + {/* {this.getActiveFilters()} */} + </div> + <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}> + <div className="top-filter-header" style={{ display: "flex", width: "100%" }}> + <div id="header">Filter Search Results</div> + <div style={{ marginLeft: "auto" }}></div> + <div className="close-icon" > + <span className="line line-1"></span> + <span className="line line-2"></span></div> + </div> + <div className="filter-options"> + <div className="filter-div"> + <div className="filter-header"> + <div className='filter-title words'>Required words</div> + </div> + <div className="filter-panel" > + <button className="all-filter">Include All Keywords</button> + </div> + </div> + <div className="filter-div"> + <div className="filter-header"> + <div className="filter-title icon">Filter by type of node</div> + </div> + <div className="filter-panel"><IconBar /></div> + </div> + <div className="filter-div"> + <div className="filter-header"> + <div className="filter-title field">Filter by Basic Keys</div> + </div> + <div className="filter-panel"><FieldFilters + titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus} + updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div> + </div> + </div> + <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}> + <button className="save-filter" >Save Filters</button> + <button className="reset-filter" >Reset Filters</button> + </div> + </div> + </div> <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ display: this._resultsOpen ? "flex" : "none", height: this.resFull ? "auto" : this.resultHeight, diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts index f1629b8f0..8ebd684bb 100644 --- a/src/server/ApiManagers/SessionManager.ts +++ b/src/server/ApiManagers/SessionManager.ts @@ -2,7 +2,7 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied, AuthorizedCore, SecureHandler } from "../RouteManager"; import RouteSubscriber from "../RouteSubscriber"; import { sessionAgent } from ".."; -import { DashSessionAgent } from "../DashSession/DashSessionAgent"; +// import { DashSessionAgent } from "../DashSession/DashSessionAgent"; const permissionError = "You are not authorized!"; @@ -25,15 +25,15 @@ export default class SessionManager extends ApiManager { protected initialize(register: Registration): void { - register({ - method: Method.GET, - subscription: this.secureSubscriber("debug", "to?"), - secureHandler: this.authorizedAction(async ({ req: { params }, res }) => { - const to = params.to || DashSessionAgent.notificationRecipient; - const { error } = await sessionAgent.serverWorker.emit("debug", { to }); - res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`); - }) - }); + // register({ + // method: Method.GET, + // subscription: this.secureSubscriber("debug", "to?"), + // secureHandler: this.authorizedAction(async ({ req: { params }, res }) => { + // const to = params.to || DashSessionAgent.notificationRecipient; + // const { error } = await sessionAgent.serverWorker.emit("debug", { to }); + // res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`); + // }) + // }); register({ method: Method.GET, diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index c55e01243..f22d1cbbc 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -1,223 +1,223 @@ -import { Email, pathFromRoot } from "../ActionUtilities"; -import { red, yellow, green, cyan } from "colors"; -import { get } from "request-promise"; -import { Utils } from "../../Utils"; -import { WebSocket } from "../Websocket/Websocket"; -import { MessageStore } from "../Message"; -import { launchServer, onWindows } from ".."; -import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs"; -import * as Archiver from "archiver"; -import { resolve } from "path"; -import { AppliedSessionAgent, MessageHandler, ExitHandler, Monitor, ServerWorker } from "resilient-server-session"; -import rimraf = require("rimraf"); - -/** - * If we're the monitor (master) thread, we should launch the monitor logic for the session. - * Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus - * our job should be to run the server. - */ -export class DashSessionAgent extends AppliedSessionAgent { - - private readonly signature = "-Dash Server Session Manager"; - private readonly releaseDesktop = pathFromRoot("../../Desktop"); - - /** - * The core method invoked when the single master thread is initialized. - * Installs event hooks, repl commands and additional IPC listeners. - */ - protected async initializeMonitor(monitor: Monitor, sessionKey: string): Promise<void> { - await this.dispatchSessionPassword(sessionKey); - monitor.addReplCommand("pull", [], () => monitor.exec("git pull")); - monitor.addReplCommand("solr", [/start|stop|index/], this.executeSolrCommand); - monitor.addReplCommand("backup", [], this.backup); - monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); - monitor.on("backup", this.backup); - monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to)); - monitor.coreHooks.onCrashDetected(this.dispatchCrashReport); - } - - /** - * The core method invoked when a server worker thread is initialized. - * Installs logic to be executed when the server worker dies. - */ - protected async initializeServerWorker(): Promise<ServerWorker> { - const worker = ServerWorker.Create(launchServer); // server initialization delegated to worker - worker.addExitHandler(this.notifyClient); - return worker; - } - - /** - * Prepares the body of the email with instructions on restoring the transmitted remote database backup locally. - */ - private _remoteDebugInstructions: string | undefined; - private generateDebugInstructions = (zipName: string, target: string): string => { - if (!this._remoteDebugInstructions) { - this._remoteDebugInstructions = readFileSync(resolve(__dirname, "./templates/remote_debug_instructions.txt"), { encoding: "utf8" }); - } - return this._remoteDebugInstructions - .replace(/__zipname__/, zipName) - .replace(/__target__/, target) - .replace(/__signature__/, this.signature); - } - - /** - * Prepares the body of the email with information regarding a crash event. - */ - private _crashInstructions: string | undefined; - private generateCrashInstructions({ name, message, stack }: Error): string { - if (!this._crashInstructions) { - this._crashInstructions = readFileSync(resolve(__dirname, "./templates/crash_instructions.txt"), { encoding: "utf8" }); - } - return this._crashInstructions - .replace(/__name__/, name || "[no error name found]") - .replace(/__message__/, message || "[no error message found]") - .replace(/__stack__/, stack || "[no error stack found]") - .replace(/__signature__/, this.signature); - } - - /** - * This sends a pseudorandomly generated guid to the configuration's recipients, allowing them alone - * to kill the server via the /kill/:key route. - */ - private dispatchSessionPassword = async (sessionKey: string): Promise<void> => { - const { mainLog } = this.sessionMonitor; - const { notificationRecipient } = DashSessionAgent; - mainLog(green("dispatching session key...")); - const error = await Email.dispatch({ - to: notificationRecipient, - subject: "Dash Release Session Admin Authentication Key", - content: [ - `Here's the key for this session (started @ ${new Date().toUTCString()}):`, - sessionKey, - this.signature - ].join("\n\n") - }); - if (error) { - this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} (${yellow(error.message)})`)); - mainLog(red("distribution of session key experienced errors")); - } else { - mainLog(green("successfully distributed session key to recipients")); - } - } - - /** - * This sends an email with the generated crash report. - */ - private dispatchCrashReport: MessageHandler<{ error: Error }> = async ({ error: crashCause }) => { - const { mainLog } = this.sessionMonitor; - const { notificationRecipient } = DashSessionAgent; - const error = await Email.dispatch({ - to: notificationRecipient, - subject: "Dash Web Server Crash", - content: this.generateCrashInstructions(crashCause) - }); - if (error) { - this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} ${yellow(`(${error.message})`)}`)); - mainLog(red("distribution of crash notification experienced errors")); - } else { - mainLog(green("successfully distributed crash notification to recipients")); - } - } - - /** - * Logic for interfacing with Solr. Either starts it, - * stops it, or rebuilds its indicies. - */ - private executeSolrCommand = async (args: string[]): Promise<void> => { - const { exec, mainLog } = this.sessionMonitor; - const action = args[0]; - if (action === "index") { - exec("npx ts-node ./updateSearch.ts", { cwd: pathFromRoot("./src/server") }); - } else { - const command = `${onWindows ? "solr.cmd" : "solr"} ${args[0] === "start" ? "start" : "stop -p 8983"}`; - await exec(command, { cwd: "./solr-8.3.1/bin" }); - try { - await get("http://localhost:8983"); - mainLog(green("successfully connected to 8983 after running solr initialization")); - } catch { - mainLog(red("unable to connect at 8983 after running solr initialization")); - } - } - } - - /** - * Broadcast to all clients that their connection - * is no longer valid, and explain why / what to expect. - */ - private notifyClient: ExitHandler = reason => { - const { _socket } = WebSocket; - if (_socket) { - const message = typeof reason === "boolean" ? (reason ? "exit" : "temporary") : "crash"; - Utils.Emit(_socket, MessageStore.ConnectionTerminated, message); - } - } - - /** - * Performs a backup of the database, saved to the desktop subdirectory. - * This should work as is only on our specific release server. - */ - private backup = async (): Promise<void> => this.sessionMonitor.exec("backup.bat", { cwd: this.releaseDesktop }); - - /** - * Compress either a brand new backup or the most recent backup and send it - * as an attachment to an email, dispatched to the requested recipient. - * @param mode specifies whether or not to make a new backup before exporting - * @param to the recipient of the email - */ - private async dispatchZippedDebugBackup(to: string): Promise<void> { - const { mainLog } = this.sessionMonitor; - try { - // if desired, complete an immediate backup to send - await this.backup(); - mainLog("backup complete"); - - const backupsDirectory = `${this.releaseDesktop}/backups`; - - // sort all backups by their modified time, and choose the most recent one - const target = readdirSync(backupsDirectory).map(filename => ({ - modifiedTime: statSync(`${backupsDirectory}/${filename}`).mtimeMs, - filename - })).sort((a, b) => b.modifiedTime - a.modifiedTime)[0].filename; - mainLog(`targeting ${target}...`); - - // create a zip file and to it, write the contents of the backup directory - const zipName = `${target}.zip`; - const zipPath = `${this.releaseDesktop}/${zipName}`; - const targetPath = `${backupsDirectory}/${target}`; - const output = createWriteStream(zipPath); - const zip = Archiver('zip'); - zip.pipe(output); - zip.directory(`${targetPath}/Dash`, false); - await zip.finalize(); - mainLog(`zip finalized with size ${statSync(zipPath).size} bytes, saved to ${zipPath}`); - - // dispatch the email to the recipient, containing the finalized zip file - const error = await Email.dispatch({ - to, - subject: `Remote debug: compressed backup of ${target}...`, - content: this.generateDebugInstructions(zipName, target), - attachments: [{ filename: zipName, path: zipPath }] - }); - - // since this is intended to be a zero-footprint operation, clean up - // by unlinking both the backup generated earlier in the function and the compressed zip file. - // to generate a persistent backup, just run backup. - unlinkSync(zipPath); - rimraf.sync(targetPath); - - // indicate success or failure - mainLog(`${error === null ? green("successfully dispatched") : red("failed to dispatch")} ${zipName} to ${cyan(to)}`); - error && mainLog(red(error.message)); - } catch (error) { - mainLog(red("unable to dispatch zipped backup...")); - mainLog(red(error.message)); - } - } - -} - -export namespace DashSessionAgent { - - export const notificationRecipient = "brownptcdash@gmail.com"; - -}
\ No newline at end of file +// import { Email, pathFromRoot } from "../ActionUtilities"; +// import { red, yellow, green, cyan } from "colors"; +// import { get } from "request-promise"; +// import { Utils } from "../../Utils"; +// import { WebSocket } from "../Websocket/Websocket"; +// import { MessageStore } from "../Message"; +// import { launchServer, onWindows } from ".."; +// import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs"; +// import * as Archiver from "archiver"; +// import { resolve } from "path"; +// import { AppliedSessionAgent, MessageHandler, ExitHandler, Monitor, ServerWorker } from "resilient-server-session"; +// import rimraf = require("rimraf"); + +// /** +// * If we're the monitor (master) thread, we should launch the monitor logic for the session. +// * Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus +// * our job should be to run the server. +// */ +// export class DashSessionAgent extends AppliedSessionAgent { + +// private readonly signature = "-Dash Server Session Manager"; +// private readonly releaseDesktop = pathFromRoot("../../Desktop"); + +// /** +// * The core method invoked when the single master thread is initialized. +// * Installs event hooks, repl commands and additional IPC listeners. +// */ +// protected async initializeMonitor(monitor: Monitor, sessionKey: string): Promise<void> { +// await this.dispatchSessionPassword(sessionKey); +// monitor.addReplCommand("pull", [], () => monitor.exec("git pull")); +// monitor.addReplCommand("solr", [/start|stop|index/], this.executeSolrCommand); +// monitor.addReplCommand("backup", [], this.backup); +// monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); +// monitor.on("backup", this.backup); +// monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to)); +// monitor.coreHooks.onCrashDetected(this.dispatchCrashReport); +// } + +// /** +// * The core method invoked when a server worker thread is initialized. +// * Installs logic to be executed when the server worker dies. +// */ +// protected async initializeServerWorker(): Promise<ServerWorker> { +// const worker = ServerWorker.Create(launchServer); // server initialization delegated to worker +// worker.addExitHandler(this.notifyClient); +// return worker; +// } + +// /** +// * Prepares the body of the email with instructions on restoring the transmitted remote database backup locally. +// */ +// private _remoteDebugInstructions: string | undefined; +// private generateDebugInstructions = (zipName: string, target: string): string => { +// if (!this._remoteDebugInstructions) { +// this._remoteDebugInstructions = readFileSync(resolve(__dirname, "./templates/remote_debug_instructions.txt"), { encoding: "utf8" }); +// } +// return this._remoteDebugInstructions +// .replace(/__zipname__/, zipName) +// .replace(/__target__/, target) +// .replace(/__signature__/, this.signature); +// } + +// /** +// * Prepares the body of the email with information regarding a crash event. +// */ +// private _crashInstructions: string | undefined; +// private generateCrashInstructions({ name, message, stack }: Error): string { +// if (!this._crashInstructions) { +// this._crashInstructions = readFileSync(resolve(__dirname, "./templates/crash_instructions.txt"), { encoding: "utf8" }); +// } +// return this._crashInstructions +// .replace(/__name__/, name || "[no error name found]") +// .replace(/__message__/, message || "[no error message found]") +// .replace(/__stack__/, stack || "[no error stack found]") +// .replace(/__signature__/, this.signature); +// } + +// /** +// * This sends a pseudorandomly generated guid to the configuration's recipients, allowing them alone +// * to kill the server via the /kill/:key route. +// */ +// private dispatchSessionPassword = async (sessionKey: string): Promise<void> => { +// const { mainLog } = this.sessionMonitor; +// const { notificationRecipient } = DashSessionAgent; +// mainLog(green("dispatching session key...")); +// const error = await Email.dispatch({ +// to: notificationRecipient, +// subject: "Dash Release Session Admin Authentication Key", +// content: [ +// `Here's the key for this session (started @ ${new Date().toUTCString()}):`, +// sessionKey, +// this.signature +// ].join("\n\n") +// }); +// if (error) { +// this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} (${yellow(error.message)})`)); +// mainLog(red("distribution of session key experienced errors")); +// } else { +// mainLog(green("successfully distributed session key to recipients")); +// } +// } + +// /** +// * This sends an email with the generated crash report. +// */ +// private dispatchCrashReport: MessageHandler<{ error: Error }> = async ({ error: crashCause }) => { +// const { mainLog } = this.sessionMonitor; +// const { notificationRecipient } = DashSessionAgent; +// const error = await Email.dispatch({ +// to: notificationRecipient, +// subject: "Dash Web Server Crash", +// content: this.generateCrashInstructions(crashCause) +// }); +// if (error) { +// this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} ${yellow(`(${error.message})`)}`)); +// mainLog(red("distribution of crash notification experienced errors")); +// } else { +// mainLog(green("successfully distributed crash notification to recipients")); +// } +// } + +// /** +// * Logic for interfacing with Solr. Either starts it, +// * stops it, or rebuilds its indicies. +// */ +// private executeSolrCommand = async (args: string[]): Promise<void> => { +// const { exec, mainLog } = this.sessionMonitor; +// const action = args[0]; +// if (action === "index") { +// exec("npx ts-node ./updateSearch.ts", { cwd: pathFromRoot("./src/server") }); +// } else { +// const command = `${onWindows ? "solr.cmd" : "solr"} ${args[0] === "start" ? "start" : "stop -p 8983"}`; +// await exec(command, { cwd: "./solr-8.3.1/bin" }); +// try { +// await get("http://localhost:8983"); +// mainLog(green("successfully connected to 8983 after running solr initialization")); +// } catch { +// mainLog(red("unable to connect at 8983 after running solr initialization")); +// } +// } +// } + +// /** +// * Broadcast to all clients that their connection +// * is no longer valid, and explain why / what to expect. +// */ +// private notifyClient: ExitHandler = reason => { +// const { _socket } = WebSocket; +// if (_socket) { +// const message = typeof reason === "boolean" ? (reason ? "exit" : "temporary") : "crash"; +// Utils.Emit(_socket, MessageStore.ConnectionTerminated, message); +// } +// } + +// /** +// * Performs a backup of the database, saved to the desktop subdirectory. +// * This should work as is only on our specific release server. +// */ +// private backup = async (): Promise<void> => this.sessionMonitor.exec("backup.bat", { cwd: this.releaseDesktop }); + +// /** +// * Compress either a brand new backup or the most recent backup and send it +// * as an attachment to an email, dispatched to the requested recipient. +// * @param mode specifies whether or not to make a new backup before exporting +// * @param to the recipient of the email +// */ +// private async dispatchZippedDebugBackup(to: string): Promise<void> { +// const { mainLog } = this.sessionMonitor; +// try { +// // if desired, complete an immediate backup to send +// await this.backup(); +// mainLog("backup complete"); + +// const backupsDirectory = `${this.releaseDesktop}/backups`; + +// // sort all backups by their modified time, and choose the most recent one +// const target = readdirSync(backupsDirectory).map(filename => ({ +// modifiedTime: statSync(`${backupsDirectory}/${filename}`).mtimeMs, +// filename +// })).sort((a, b) => b.modifiedTime - a.modifiedTime)[0].filename; +// mainLog(`targeting ${target}...`); + +// // create a zip file and to it, write the contents of the backup directory +// const zipName = `${target}.zip`; +// const zipPath = `${this.releaseDesktop}/${zipName}`; +// const targetPath = `${backupsDirectory}/${target}`; +// const output = createWriteStream(zipPath); +// const zip = Archiver('zip'); +// zip.pipe(output); +// zip.directory(`${targetPath}/Dash`, false); +// await zip.finalize(); +// mainLog(`zip finalized with size ${statSync(zipPath).size} bytes, saved to ${zipPath}`); + +// // dispatch the email to the recipient, containing the finalized zip file +// const error = await Email.dispatch({ +// to, +// subject: `Remote debug: compressed backup of ${target}...`, +// content: this.generateDebugInstructions(zipName, target), +// attachments: [{ filename: zipName, path: zipPath }] +// }); + +// // since this is intended to be a zero-footprint operation, clean up +// // by unlinking both the backup generated earlier in the function and the compressed zip file. +// // to generate a persistent backup, just run backup. +// unlinkSync(zipPath); +// rimraf.sync(targetPath); + +// // indicate success or failure +// mainLog(`${error === null ? green("successfully dispatched") : red("failed to dispatch")} ${zipName} to ${cyan(to)}`); +// error && mainLog(red(error.message)); +// } catch (error) { +// mainLog(red("unable to dispatch zipped backup...")); +// mainLog(red(error.message)); +// } +// } + +// } + +// export namespace DashSessionAgent { + +// export const notificationRecipient = "brownptcdash@gmail.com"; + +// }
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 313a2f0e2..454d99305 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -22,7 +22,7 @@ import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { Logger } from "./ProcessFactory"; import { yellow } from "colors"; -import { DashSessionAgent } from "./DashSession/DashSessionAgent"; +// import { DashSessionAgent } from "./DashSession/DashSessionAgent"; import SessionManager from "./ApiManagers/SessionManager"; import { AppliedSessionAgent } from "resilient-server-session"; @@ -141,7 +141,7 @@ export async function launchServer() { * So, the 'else' clause is exactly what we've always run when executing npm start. */ if (process.env.RELEASE) { - (sessionAgent = new DashSessionAgent()).launch(); + // (sessionAgent = new DashSessionAgent()).launch(); } else { launchServer(); } |