diff --git a/doc/source/basicgameplay/terminal.rst b/doc/source/basicgameplay/terminal.rst index 24fd1c6ef..6eeb42e5b 100644 --- a/doc/source/basicgameplay/terminal.rst +++ b/doc/source/basicgameplay/terminal.rst @@ -403,7 +403,7 @@ to convert from script to text file, or vice versa. This function can also be used to rename files. .. note:: Unlike the Linux :code:`mv` command, the *destination* argument must be the - full filepath. It cannot be a directory. + full filepath. It cannot be a directory. Examples:: @@ -511,6 +511,8 @@ sudov Prints whether or not you have root access to the current server. +.. _tail_terminal_command: + tail ^^^^ diff --git a/doc/source/netscript/basicfunctions/tail.rst b/doc/source/netscript/basicfunctions/tail.rst new file mode 100644 index 000000000..18e32a9c1 --- /dev/null +++ b/doc/source/netscript/basicfunctions/tail.rst @@ -0,0 +1,29 @@ +tail() Netscript Function +================================== + +.. js:function:: tail([fn], [hostname/ip=current ip], [...args]) + + :param string fn: Optional. Filename of script to get logs from. + :param string ip: Optional. IP or hostname of the server that the script is on + :param args...: Arguments to identify which scripts to get logs for + :RAM cost: 0 GB + + Opens a script's logs. This is functionally the same as the + :ref:`tail_terminal_command` Terminal command. + + If the function is called with no arguments, it will open the current script's logs. + + Otherwise, the `fn`, `hostname/ip,` and `args...` arguments can be used to get the logs + from another script. Remember that scripts are uniquely identified by both + their names and arguments. + + Examples:: + + // Open logs from foo.script on the current server that was run with no args + tail("foo.script"); + + // Open logs from foo.script on the foodnstuff server that was run with no args + tail("foo.script", "foodnstuff"); + + // Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"] + tail("foo.script", "foodnstuff", 1, "test"); diff --git a/doc/source/netscript/netscriptfunctions.rst b/doc/source/netscript/netscriptfunctions.rst index 58067d09d..c7658ed8d 100644 --- a/doc/source/netscript/netscriptfunctions.rst +++ b/doc/source/netscript/netscriptfunctions.rst @@ -24,6 +24,7 @@ This includes information such as function signatures, what they do, and their r enableLog() isLogEnabled() getScriptLogs() + tail() scan() nuke() brutessh() diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 6d1b1ed0d..1229469ec 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -145,9 +145,10 @@ import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { is2DArray } from "./utils/helpers/is2DArray"; import { dialogBoxCreate } from "../utils/DialogBox"; -import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo"; -import { arrayToString } from "../utils/helpers/arrayToString"; import { formatNumber, isHTML } from "../utils/StringHelperFunctions"; +import { logBoxCreate } from "../utils/LogBox"; +import { arrayToString } from "../utils/helpers/arrayToString"; +import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo"; import { isString } from "../utils/helpers/isString"; import { createElement } from "../utils/uiHelpers/createElement"; @@ -251,7 +252,7 @@ function NetscriptFunctions(workerScript) { /** * Gets the Server for a specific hostname/ip, throwing an error * if the server doesn't exist. - * @param {string} Hostname or IP of the server + * @param {string} ip - Hostname or IP of the server * @param {string} callingFnName - Name of calling function. For logging purposes * @returns {Server} The specified Server */ @@ -264,6 +265,59 @@ function NetscriptFunctions(workerScript) { return server; } + /** + * Searches for and returns the RunningScript object for the specified script. + * If the 'fn' argument is not specified, this returns the current RunningScript. + * @param {string} fn - Filename of script + * @param {string} ip - Hostname/ip of the server on which the script resides + * @param {any[]} scriptArgs - Running script's arguments + * @returns {RunningScript} + * Running script identified by the parameters, or null if no such script + * exists, or the current running script if the first argument 'fn' + * is not specified. + */ + const getRunningScript = function(fn, ip, callingFnName, scriptArgs) { + // Sanitize arguments + if (typeof callingFnName !== "string" || callingFnName === "") { + callingFnName = "getRunningScript"; + } + + if (!Array.isArray(scriptArgs)) { + throw makeRuntimeRejectMsg( + workerScript, + `Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` + + `This is probably a bug. Please report to game developer` + ); + } + + if (fn != null && typeof fn === "string") { + // Get Logs of another script + if (ip == null) { ip = workerScript.serverIp; } + const server = getServer(ip, callingFnName); + + return findRunningScript(fn, scriptArgs, server); + } + + // If no arguments are specified, return the current RunningScript + return workerScript.scriptRef; + } + + /** + * Helper function for getting the error log message when the user specifies + * a nonexistent running script + * @param {string} fn - Filename of script + * @param {string} ip - Hostname/ip of the server on which the script resides + * @param {any[]} scriptArgs - Running script's arguments + * @returns {string} Error message to print to logs + */ + const getNoSuchRunningScriptErrorMessage = function(fn, ip, scriptArgs) { + if (!Array.isArray(scriptArgs)) { + scriptArgs = []; + } + + return `Cannot find running script ${fn} on server ${ip} with args: ${arrayToString(scriptArgs)}`; + } + /** * Checks if the player has TIX API access. Throws an error if the player does not */ @@ -699,29 +753,23 @@ function NetscriptFunctions(workerScript) { } return workerScript.disableLogs[fn] ? false : true; }, - getScriptLogs: function(fn, ip) { - if (fn != null && typeof fn === 'string') { - // Get Logs of another script - if (ip == null) { ip = workerScript.serverIp; } - const server = getServer(ip); - if (server == null) { - workerScript.log(`getScriptLogs() failed. Invalid IP or hostname passed in: ${ip}`); - throw makeRuntimeRejectMsg(workerScript, `getScriptLogs() failed. Invalid IP or hostname passed in: ${ip}`); - } - - let argsForTarget = []; - for (let i = 2; i < arguments.length; ++i) { - argsForTarget.push(arguments[i]); - } - const runningScriptObj = findRunningScript(fn, argsForTarget, server); - if (runningScriptObj == null) { - workerScript.scriptRef.log(`getScriptLogs() failed. No such script ${fn} on ${server.hostname} with args: ${arrayToString(argsForTarget)}`); - return ""; - } - return runningScriptObj.logs.slice(); + getScriptLogs: function(fn, ip, ...scriptArgs) { + const runningScriptObj = getRunningScript(fn, ip, "getScriptLogs", scriptArgs); + if (runningScriptObj == null) { + workerScript.log(`getScriptLogs() failed. ${getNoSuchRunningScriptErrorMessage(fn, ip, scriptArgs)}`); + return ""; } - return workerScript.scriptRef.logs.slice(); + return runningScriptObj.logs.slice(); + }, + tail: function(fn, ip, ...scriptArgs) { + const runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs); + if (runningScriptObj == null) { + workerScript.log(`tail() failed. ${getNoSuchRunningScriptErrorMessage(fn, ip, scriptArgs)} `); + return; + } + + logBoxCreate(runningScriptObj); }, nuke: function(ip){ updateDynamicRam("nuke", getRamCost("nuke")); diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index f9d7eb76a..6f5acd353 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -62,7 +62,7 @@ let NetscriptFunctions = "hack|hackAnalyzeThreads|hackAnalyzePercent|hackChance|" + "sleep|grow|weaken|growthAnalyze|print|tprint|scan|nuke|brutessh|" + "ftpcrack|" + - "clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|" + + "clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|tail|" + "relaysmtp|httpworm|sqlinject|run|exec|spawn|kill|killall|exit|" + "scp|ls|ps|hasRootAccess|" + "getIp|getHackingMultipliers|getBitNodeMultipliers|getStats|isBusy|" + diff --git a/src/ScriptEditor/CodeMirrorNetscriptMode.js b/src/ScriptEditor/CodeMirrorNetscriptMode.js index fea3331a3..33c29e1e0 100644 --- a/src/ScriptEditor/CodeMirrorNetscriptMode.js +++ b/src/ScriptEditor/CodeMirrorNetscriptMode.js @@ -58,6 +58,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "enableLog": atom, "isLogEnabled": atom, "getScriptLogs": atom, + "tail": atom, "relaysmtp": atom, "httpworm": atom, "sqlinject": atom, diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx index bbf522596..fc5208c7c 100644 --- a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -25,7 +25,6 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement { const workerScript = props.workerScript; const scriptRef = workerScript.scriptRef; - const logClickHandler = logBoxCreate.bind(null, scriptRef); const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server);