diff --git a/webpanel/5.7.0/422.6ffa6fbbf00a7aa4137e.bundle.js b/webpanel/5.7.0/422.6ffa6fbbf00a7aa4137e.bundle.js new file mode 100644 index 00000000..d4640e2e --- /dev/null +++ b/webpanel/5.7.0/422.6ffa6fbbf00a7aa4137e.bundle.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunktgstation_server_control_panel=self.webpackChunktgstation_server_control_panel||[]).push([[422],{2422:function(e){e.exports=JSON.parse('{"routes.home":"Home","routes.user_manager":"User Manager","routes.login":"Log In","routes.admin":"Administration","routes.admin.update":"TGS Update","routes.admin.logs":"TGS Logs","routes.config":"Webpanel Config","routes.passwd":"Change Password","routes.usermanager":"Users","routes.useredit":"User Editor","routes.usercreate":"Create User","routes.instancelist":"Instances","routes.instancecreate":"Add New Instance","routes.instanceedit":"Edit Instance","routes.instancejobs":"Jobs","routes.setup":"PostInstall Setup","routes.info":"Info","login.header":"Login to Continue","login.type.generic":"Password Login","login.type.oauth":"OAuth Login","login.oauth":"Sign in with {provider}","login.submit":"Login","login.password":"Password","login.password.repeat":"Confirm Password","login.password.repeat.match":"Passwords do not match!","login.password.repeat.short":"Password is too short! It must have a length of atleast ","login.title":"Login","login.username":"Username","navbar.home":"Home","navbar.purgecache":"Purge Client Cache","navbar.refresh":"Refresh","navbar.logout":"Logout","navbar.update":"Server Update Available","app.job.started":"Started: ","app.job.startedby":"Started By: ","app.job.completed":"Finished: ","app.job.cancelled":"Cancelled: ","app.job.cancelledby":"Cancelled By: ","error.http.access_denied.desc":"Access to this resource is denied","error.unhandled_response.desc":"The application received an unexpected response","error.unhandled_global_response.desc":"The application received an unexpected global response","error.login.no_creds.desc":"Attempted to login without any credentials","error.login.bad_oauth":"An error occurred while logging in using OAuth","error.login.bad_user_pass.desc":"Invalid credentials!","error.login.user_disabled.desc":"This user account is disabled","error.user.no_sys_ident.desc":"This system user was not found","error.user.not_found.desc":"This TGS user was not found","error.http.bad_request":"Bad Request","error.http.data_integrity":"A data integrity check failed while performing the operation","error.http.api_mismatch":"API version mismatch","error.http.server_error":"BUG: Server error","error.http.unimplemented":"This feature is unimplemented.","error.http.server_not_ready":"The server is still starting/stoping!","error.http.access_denied":"Access denied","error.http.not_acceptable":"BUG: The server has found the request to be unacceptable.","error.axios":"Axios error","error.unhandled_response":"The application received an unexpected response","error.unhandled_global_response":"The application received an unexpected global response","error.login.no_creds":"Attempted to login without any credentials","error.login.bad_user_pass":"Invalid credentials!","error.login.user_disabled":"This user account is disabled","error.login.rate_limit":"Failed to login using external provider due to rate limiting. Please try again later","error.user.no_sys_ident":"This system user was not found","error.user.not_found":"This TGS user was not found","error.group.not_found":"The requested group cannot be found.","error.group.not_empty":"The requested group cannot be deleted as it contains users.","error.admin.rate":"The server has exceeded github\'s rate limiting. Please try again later.","error.admin.error":"The server has ran into an error while using github\'s API.","error.admin.watchdog.avail":"This operation is unavailable due to the launch configuration of TGS.","error.admin.update.notfound":"This TGS version does not seem to exist.","error.admin.logs.io":"An IO error occured while processing logs","error.somethingwentwrong":"Uh oh.... Something went wrong!","error.notfound":"This page has not been found!","error.github":"An error occured while interacting with the Github API","error.app":"An error occured in the application","error.app.undefined":"A \\"Ghost\\" error occured in the application","error.app.default_creds":"You are using the default TGS credentials. Please click \'Change Password\' at earliest convenience!","error.job.not_found":"The specified job could not be found","error.job.complete":"Unable to delete the job, perhaps it already completed","error.transfer.not_available":"Unable to transfer file as it is no longer or never was valid","error.transfer.upload_failed":"An error occured while uploading a file","error.no_db_entity":"The database entity for the requested instance could not be retrieved. The instance was likely detached","error.api.empty":"No description available","error.no_apipath":"No API path set, set an API path on the configuration page.","error.compile_job_not_found":"Compile job not found.","error.bad_tgs_yml_version":"Incorrect .tgs.yml version. Only version 1 is supported.","error.bad_yml":"Malformed .yml.","error.bad_channels_json":"Malformed channels JSON.","error.no_engine_version":"The target engine version does not exist on the server.","error.bad_hub_connection":"Lost connection to the SignalR hub.","generic.save":"Save","generic.savetab":"Save Tab","generic.saveall":"Save All","generic.details":"Details","generic.downloading":"Downloading \\"{file}\\"...","generic.downloaded":"Downloaded \\"{file}\\"","generic.errordetails":"Error Details ({info})","generic.debugwarn":"Be careful to censor out any credentials or tokens when copying errors!","generic.close":"Close","generic.clone":"Clone","generic.goback":"Go Back","generic.accessdenied":"This user does not have access to this page.","generic.persist":"Persist","generic.continue":"Continue","generic.view":"View","generic.download":"Download","generic.name":"Name","generic.datetime":"Date/Time","generic.entry":"Entry","generic.action":"Action","generic.createdby":"Created By","generic.created":"Created","generic.disabled":"Disabled","generic.enabled":"Enabled","generic.grouped":"Grouped","generic.tgs":"TGS","generic.group":"Group","generic.groupid":"Group ID {id}","generic.info":"Info","generic.system.short":"SYSTEM","generic.edit":"Edit","generic.access":"Access","generic.systemidentifier":"System Identifier","generic.appname":"tgstation-server","generic.userid":"User ID ","generic.numusers":"{count} Users","generic.setall":"Set all","generic.true":"True","generic.false":"False","generic.reset":"Reset","generic.disable":"Disable","generic.enable":"Enable","generic.configmode":"Configuration Mode","generic.online":"Online","generic.offline":"Offline","generic.path":"Path","generic.select":"Select","generic.instance":"Instance","generic.goto.title":"Go to page","generic.goto":"Goto","generic.wip":"Work In Progress!","generic.readonly":"(Read-Only)","generic.invalid_form":"This form contains invalid values!","generic.no_perm":"You do not have the permission to do this","generic.wip.desc":"The TGS webpanel is still a work in progress. The feature you are trying to use is not yet available. Until it becomes available, please use the TGS desktop client at ","generic.assert.noinstance":"No instance. Perhaps an error occured.","generic.assert.nouser":"No user. Perhaps an error occured.","generic.assert.nopermissionset":"No permission set. Perhaps an error occured.","generic.latest":"Latest","generic.testmerged":"Testmerged","generic.commit":"Commit","generic.areyousure":"Are you sure?","generic.cancel":"Cancel","generic.not_applicable":"N/A","view.setup.navigationblock":"Navigation has been disabled for the duration of the setup.","view.setup.title":"Step By Step Setup Wizard","view.setup.quit":"Quit Setup","view.setup.quitconfirm":"Are you sure you want to exit the setup? You will not be able to return.","view.setup.disableadmin":"Disable default Admin account","view.setup.nextpage":"Next Page","view.setup.step.1":"Step 1. Create yourself a user account","view.setup.step.2":"Step 2. Login using your new user account","view.setup.step.3":"Step 3. Disable the default Admin account","view.setup.step.4":"Step 4. Configure clientside settings (Optional)","view.setup.step.5":"Setup Complete!","view.admin.hostos":"Host Machine OS: ","view.admin.remote":"Remote repository: ","view.admin.version.current":"Current Version: ","view.admin.version.latest":"Latest Version: ","view.admin.reboot.button":"Restart TGS","view.admin.reboot.modal.title":"Confirmation","view.admin.reboot.modal.body":"Are you sure you wish to restart TGS?","view.admin.update.button":"Update TGS","view.admin.update.selectversion":"Select Version","view.admin.update.latest":" (Latest)","view.admin.update.current":" (Current)","view.admin.update.releasenotes":"Release Notes","view.admin.update.wait":"Please take the time to read the release notes before proceeding","view.admin.update.showall":"Show all versions","view.admin.update.selectversion.deny":"You do not have permission to update to a GitHub version","view.admin.update.upload":"Upload Update Package","view.admin.update.upload.deny":"You do not have permission to update using uploaded packages","view.admin.update.major_warn.title":"WARNING: Attempting Major TGS Version Change","view.admin.update.major_warn.body":"You are attempting to switch the major version of TGS from {currentMajor} to {targetMajor}! READ THE RELEASE NOTES CAREFULLY! Most TGS major upgrades constitute a change in the required .NET runtime. If this runtime is not installed PRIOR to updating, TGS will FAIL to restart after upgrading until it is! There also may be REQUIRED configuration changes that, if not made, may also result in a failure to restart!","view.admin.logs.button":"TGS Logs","view.user.list.cantlist":"This user does not have the permission to list users, only the current user is listed/editable.","view.user.edit.cantedit":"This user does not have the permission to edit users.","view.user.edit.oauth.connections":"OAuth Connections","view.user.edit.oauth.current":"OAuth 2.0 Connections","view.user.edit.oauth.add":"Add Connection","view.user.edit.oauth.id":"Service User ID:","view.user.edit.oauth.provider":"Provider","view.user.edit.oauth.provider.discord":"Discord","view.user.edit.oauth.provider.github":"GitHub","view.user.edit.oauth.provider.tgforums":"/tg/ Forums","view.user.edit.oauth.provider.keycloak":"Keycloak","view.user.edit.oauth.provider.invisioncommunity":"Invision Community","view.user.passwd.title":"Editing password for ","view.user.create.tgs":"Create user with TGS identifier","view.user.create.sys":"Create user with system identifier","view.utils.deployment_viewer.dmapi_outdated":"Your codebase\'s DMAPI interop version ({codebase}) is not present or less than tgstation-server\'s version ({tgs}). Please update your codebase with the latest TGS DMAPI library for full functionality. Click here to go to the latest TGS DMAPI release.","view.utils.deployment_viewer.no_jobs":"No deployments have been created!","view.utils.deployment_viewer.test_merges_hint.show":"Show Test Merges","view.utils.deployment_viewer.test_merges_hint.hide":"Hide Test Merges","view.utils.deployment_viewer.table.id":"Id","view.utils.deployment_viewer.table.byond":"Engine Version","view.utils.deployment_viewer.table.started_at":"Started At","view.utils.deployment_viewer.table.completed_at":"Completed At","view.utils.deployment_viewer.table.started_by":"Started By","view.utils.deployment_viewer.table.project":"Project Name","view.utils.deployment_viewer.table.pr.number":"Test Merge #","view.utils.deployment_viewer.table.pr.title":"Title","view.utils.deployment_viewer.table.pr.merged_by":"Merged By","view.utils.deployment_viewer.table.pr.merged_at":"Merged At","view.utils.deployment_viewer.table.pr.comment":"Comment","view.utils.deployment_viewer.table.revision":"SHA","view.utils.deployment_viewer.table.origin":"Origin SHA","view.utils.deployment_viewer.table.security":"Minimum Security","view.utils.deployment_viewer.table.dmapi":"DMAPI Interop Version","view.instance.no_metadata":"You do not have the permission to read all settings, only editable fields will be shown. Said fields will only contain default values.","view.instance.no_compile_jobs":"You do not have the permission to view deployment information!","view.instanceedit.title":"Editing Instance {instancename} ({instanceid})","view.instanceedit.tabs.info":"Information","view.instanceedit.tabs.repository":"Repository","view.instanceedit.tabs.deployment":"Deployment","view.instanceedit.tabs.dreamdaemon":"Server","view.instanceedit.tabs.engine":"Engine","view.instanceedit.tabs.chatbots":"Chat Bots","view.instanceedit.tabs.files":"Files & Scripts","view.instanceedit.tabs.users":"Permissions","view.instanceedit.tabs.jobs":"Jobs History","view.instanceedit.tabs.config":"Config","view.instance.create.loading":"Creating Instance...","view.instance.create.title":"New Instance","view.instance.create.name":"Instance Name","view.instance.create.manual":"Manual Setup","view.instance.create.quick":"Quick Setup","view.instance.create.quick.active":"Performing Instance Quick Setup","view.instance.create.quick.stage.yml":"Downloading .tgs.yml...","view.instance.create.quick.stage.download_scripts":"Downloading script \\"{script}\\"...","view.instance.create.quick.stage.create_instance":"Creating instance...","view.instance.create.quick.stage.cloning":"Starting repository clone...","view.instance.create.quick.stage.byond":"Starting {version} BYOND install...","view.instance.create.quick.stage.settings":"Updating compiler/server settings...","view.instance.create.quick.stage.upload_scripts":"Uploading EventScript \\"{script}\\"...","view.instance.create.quick.stage.static":"Creating GameStaticFiles directory \\"{dir}\\"...","view.instance.create.quick.stage.static.transfer":"Transferring \\"{path}\\" from GitHub to GameStaticFiles/{targetPath}...","view.instance.create.quick.submit":"Start Quick Setup","view.instance.create.quick.notice":"Quick setup requires the following:{br}\\t- A GitHub hosted repository.{br}\\t- A codebase containing a .tgs.yml file in its root.{br}\\t- A GitHub personal access token with read access to the repository set in the webpanel settings.","view.instance.create.quick.warning":"Quick setup may install executable scripts or set the BYOND security level to a less secure value. Ensure you trust the codebase you are targeting.","view.instance.create.repo_owner":"GitHub Repository Owner","view.instance.create.repo_name":"GitHub Repository Name","view.instance.create.repo_branch":"GitHub Repository Reference (branch/tag, Optional)","view.instance.create.access_user":"GitHub Token Username (Optional)","view.instance.create.access_token":"GitHub Token (Optional)","view.instance.create.path":"Instance Path on Server","view.instance.create.path.prefix":"Prefix:","view.instance.create.submit":"Create Instance","view.instance.list.grant":"Grant yourself permission to access this instance","view.instance.list.grant.deny":"Requires the \\"Grant All Permissions\\" instance manager right","view.instance.list.title":"Instance List","view.instance.list.set.online":"Bring Online","view.instance.list.set.offline":"Take Offline","view.instance.configmode.0":"Disabled","view.instance.configmode.1":"Authorized users can read/write","view.instance.configmode.2":"Authorized users can read/write using their system user","view.instance.jobs.title":"Job list","view.instance.jobs.jobtotal":"{amount} jobs","view.instance.jobs.error":"An error occured","view.instance.jobs.clearfinished":"Clear finished jobs","view.instance.jobs.reconnect_in":"Attempting reconnect in {seconds}s...","view.instance.jobs.reconnect_now":"Attempting reconnection...","view.instance.jobs.reconnected_auth":"Reconnected, authenticating...","view.instance.moving":"[MOVING INSTANCE...]","view.instance.config.instancesettings":"Instance Settings","view.instance.config.instanceusers":"Instance Users","view.instance.config.chatbots":"Chat Bots","view.instance.engine":"Version Selector","view.instance.engine.add_byond":"Install new BYOND version","view.instance.engine.add_od":"Install new OpenDream version","view.instance.engine.upload":"Upload custom version","view.instance.engine.custom":"Uploaded from zip file","view.instance.engine.current_and_list_denied":"This user does not have the permission to access information about BYOND versions","view.instance.engine.list_denied":"This user does not have the permission to list all installed BYOND versions","view.instance.engine.current_denied":"This user does not have the permission to see the active BYOND version","view.instance.engine.current_version":"Active Version: {version}","view.instance.chat":"Chat Bots","view.instance.chat.create":"Add Bot","view.instance.chat.create.invalid.discord":"Invalid Discord channel ID!","view.instance.chat.create.invalid.irc":"Invalid IRC channel!","view.instance.chat.create.missing.address":"Missing IRC Server Address!","view.instance.chat.create.missing.channel":"Missing channel ID!","view.instance.chat.create.missing.name":"Missing Bot Name!","view.instance.chat.create.missing.nick":"Missing IRC Bot Nickname!","view.instance.chat.create.missing.token":"Missing Discord Bot Token!","view.instance.chat.create.channel":"Add Channel","view.instance.chat.delete":"Delete Bot","view.instance.chat.delete.deny":"You do not have permission to delete chat bots","view.instance.chat.delete.confirm":"Are you sure you want to delete chat bot \\"{botName}\\"?","view.instance.chat.delete.channel":"Delete Channel","view.instance.chat.delete.channel.confirm":"Are you sure you want to delete channel \\"{channelName}\\"?","view.instance.chat.limit":"Maximum of {max} chat bots reached!","view.instance.chat.limit.channels":"Maximum of {max} channels reached!","view.instance.chat.reload":"Load Connection String","view.instance.chat.reload.deny":"You do not have permission to view connection strings","view.instance.chat.select_item":"Select an Item on the Left","view.instance.chat.channels.export":"Export Channels to Clipboard","view.instance.chat.channels.import":"Import Channels from Clipboard","view.instance.chat.channels.deny":"You do not have permission to edit chat bot channels!","view.instance.files.create":"Create Item","view.instance.files.delete":"Delete File","view.instance.files.delete.confirm":"Are you sure you want to delete the file \\"{path}\\"","view.instance.files.delete.directory":"Delete Empty Directory","view.instance.files.delete.directory.populated":"Only empty directories may be deleted","view.instance.files.delete.directory.populated.unloaded":"Expand the directory to see if it contains files before deleting it","view.instance.files.delete.directory.confirm":"Are you sure you want to delete the empty directory \\"{directoryName}\\"?","view.instance.files.disallowed":"The instance settings prevent you from using the file browser","view.instance.files.disallowed.directory":"You do not have permission to view directory contents. The webpanel does not support file browsing without this permission.","view.instance.files.disallowed.directory.delete":"You do not have permission to delete directories","view.instance.files.disallowed.read_no_browse":"You do not have permission to browse nor download files.","view.instance.files.disallowed.read":"You do not have permission to download files.","view.instance.files.disallowed.write":"You do not have permission to create, modify, or delete files.","view.instance.files.download":"Download File","view.instance.files.download.directory":"Download Zip","view.instance.files.download.location":"Downloaded files will be saved in the OS\'s default download location","view.instance.files.file_browser":"File Browser","view.instance.files.load_more":"(Click to Load Directory)","view.instance.files.replace":"Overwrite with Uploaded File","view.instance.files.replace.stale":"Cannot replace file due to being unable to refresh its status!","view.instance.files.select_item":"Select an Item on the Left","view.instance.files.upload":"Upload and Overwrite File","view.instance.files.zip.confirm":"Are you sure you want to download the directory \\"{path}\\" as a zip? This can be a very intense operation for large folder structures and may not succeed.","view.instance.info":"Instance Metadata","view.instance.perms":"Instance Permissions","view.instance.perms.create":"Create Instance Permission Set","view.instance.perms.delete":"Delete Instance Permission Set","view.instance.perms.grant":"Grant Full Permissions","view.instance.perms.grant.desc":"You have access to grant yourself full permissions on this instance.","view.instance.perms.missing":"This permission set is not registered with the instance","view.instance.server.status":"Status: ","view.instance.server.status.Offline":"Offline","view.instance.server.status.Restoring":"Restoring","view.instance.server.status.Online":"Online","view.instance.server.status.DelayedRestart":"Delayed Restart","view.instance.server.status.undefined":"No permission","view.instance.server.settings":"Settings","view.instance.server.actions":"Actions","view.instance.server.start":"Start","view.instance.server.stop":"Stop","view.instance.server.restart":"Restart","view.instance.server.dump":"Dump Process","view.instance.server.deployment_info":"Deployment Information","view.instance.server.deployment_info.active":"Active Deployment","view.instance.server.deployment_info.staged":"Staged Deployment","view.instance.server.no_metadata_and_no_settings":"You do not have the permission to list or edit Dream Daemon settings for this instance.","view.instance.server.no_metadata_actions":"You do not have permission to fetch the status of the server, some actions may not work depending on the state of the server.","view.instance.server.no_metadata_graceful":"You do not have permission to fetch the current graceful action.","view.instance.server.no_graceful":"You do not have permission to get or set the current graceful action.","view.instance.server.no_actions":"You do not have permission to commit any actions.","view.instance.server.broadcast":"Broadcast","view.instance.server.prompt.restart":"Are you sure you wish to immediately restart the server?","view.instance.server.prompt.stop":"Are you sure you wish to immediately stop the server?","view.instance.graceful":"Graceful Action","view.instance.graceful.desc":"This action will be applied the next time the server restarts","view.instance.graceful.Restart":"Restart","view.instance.graceful.Stop":"Shutdown","view.instance.graceful.None":"None","view.instance.repo.canthookclone":"Unable to find clone job, refresh page when clone is complete. Please report this!","view.instance.repo.repoinfo":"Repository Information","view.instance.repo.reposettings":"Repository Settings","view.instance.repo.testmerges":"Test Merges","view.instance.repo.testmerges.badprovider":"Guided test merges are only supported on github","view.instance.repo.info.origin":"Origin URL","view.instance.repo.info.owner":"Repository Owner","view.instance.repo.info.name":"Repository Name","view.instance.repo.tm.by":"Testmerged by:","view.instance.repo.tm.comment":"Comment:","view.instance.repo.tm.commit":"Commit:","view.instance.repo.tm.modal.title":"Add or Update Testmerge","view.instance.repo.tm.modal.label":"Select Commit","view.instance.repo.tm.modal.comment":"Comment","view.instance.repo.tm.modal.tip":"Tip: When clicking the add or update button, hold shift to testmerge the latest commit bypassing this popup!","view.instance.repo.pending.title":"Pending Changes","view.instance.repo.pending.none":"No pending changes","view.instance.repo.pending.deploy":"Queue a deployment of repository code to the server","view.instance.repo.pending.reapply":"Retestmerge #{number} ({title}) at {commit}","view.instance.repo.pending.reset":"Reset repository to local tracked reference","view.instance.repo.pending.reset.nobranch":"Recheckout commit {commit}","view.instance.repo.pending.update":"Reset repository to remote tracked reference","view.instance.repo.pending.added":"Testmerge #{number} ({title}) at {commit}","view.instance.repo.pending.added.manual":"Manual testmerge of PR #{number}","view.instance.repo.pending.updated":"Update #{number} ({title}) to {commit}","view.instance.repo.pending.removed":"Removes #{number} ({title})","view.instance.repo.pending.renamed":"Change the comment on #{number}","view.instance.repo.update.remote":"Update to Remote","view.instance.repo.update.local":"Reset to Local Origin","view.instance.repo.update.local.tip":"Using this option may take you off the current repository reference. In order to update to latest, you will have to manually checkout said reference again.","view.instance.repo.update.none":"No Initial Change","view.instance.repo.reset":"Reset repository to origin","view.instance.repo.reset.desc":"This option will reset the repository to the tracked origin, updating the repository and clearing any test merged PRs","view.instance.repo.manual":"Manual Test Merge Entry","view.instance.repo.manual.desc":"Use this box to manually test merge a pull/merge request by entering its number and clicking \\"Add Test Merge\\"","view.instance.repo.addmanual":"Add Test Merge","view.instance.repo.testmergelabel":"Labelled","view.instance.repo.norepoinfo":"You lack the permission to display information about the repository","view.instance.repo.delete.title":"Delete Repository","view.instance.repo.delete.desc":"This will delete the local copy of the repository. Instance settings, code modifications, event scripts and static files will be preserved.","view.instance.repo.delete":"Delete Repo","view.instance.repo.clone":"Clone Remote Repository","view.instance.repo.noautomerge":"Automatic test merge management is unavailable due to missing permissions","view.instance.repo.deployAfter":"Compile & Deploy after changes","view.instance.repo.deployAfter.desc":"Compile and deploy the repository to the server after completing the test merge","view.instance.deploy.title":"Deployment Settings","view.instance.deploy.deploy":"Compile & Deploy repository","view.info.client":"Client Info","view.info.server":"Server Info","view.info.swarm":"Swarm Info","view.info.controller":"[CONTROLLER]","view.info.version":"Version: ","view.info.httpapiversion":"HTTP API Version: ","view.info.dmapiversion":"DM API Version: ","view.info.minpassword":"Minimum Password Length: ","view.info.instancelimit":"Instance Limit: ","view.info.userlimit":"User Limit: ","view.info.grouplimit":"Group Limit: ","view.info.oauth":"OAuth Support: ","view.meme_0":"Toxic Gamers\' Sanctuary","view.meme_1":"The Great Spaceman","view.meme_2":"The Griefers\' Stronghold","view.meme_3":"The Gourmet Spaceport","view.meme_4":"The Galactic Sh*tshow","view.meme_5":"The Grand Syndicate","view.meme_6":"The Giant Spacepickle","view.meme_7":"The Goofy Spacemen","view.meme_8":"The Godly Station","view.meme_9":"The Grumpy Scientists","view.meme_10":"Terrible Griefer Society","view.meme_11":"Taco-Generating Spacecraft","view.meme_12":"Thirsty Gamers\' Saloon","view.meme_13":"Titanic Gaming Server","view.meme_14":"Tragic Space Odyssey","view.meme_15":"Tinfoil Hat Guild","view.meme_16":"Turbocharged Game Station","view.meme_17":"Time-Traveling Game Show","view.meme_18":"Totally Gnarly Setup","view.meme_19":"Terrifying Ghost Ship","view.meme_20":"Tasty Grilled Sandwich","view.meme_21":"Treasure-Gathering Spacefarers","view.meme_22":"Tornado-Generating Storm","view.meme_23":"Twisted Gaming Society","view.meme_24":"Tgstation Sucks","view.meme_25":"Terra-Gov Server","view.report":"Report Issue","perms.admin":"Administration Permissions","perms.admin.writeusers":"Edit Users","perms.admin.writeusers.desc":"Ability to edit users, if View Users is granted, all users can be edited, otherwise, only the current user can be edited","perms.admin.restarthost":"Restart TGS","perms.admin.restarthost.desc":"Ability to restart TGS","perms.admin.changeversion":"Update TGS","perms.admin.changeversion.desc":"Abilty to update TGS to a newer version","perms.admin.editownpassword":"Change Own Password","perms.admin.editownpassword.desc":"Ability to change their own password","perms.admin.readusers":"View Users","perms.admin.readusers.desc":"Ability to view all users","perms.admin.downloadlogs":"Access TGS logs","perms.admin.downloadlogs.desc":"Ability to view and download all TGS logs","perms.admin.editownoauthconnections":"Edit own external identity providers","perms.admin.editownoauthconnections.desc":"Ability to edit their own identity providers(oauth)","perms.admin.uploadversion":"Upload Version .zip","perms.admin.uploadversion.desc":"Ability to update the server with an uploaded .zip update package.","perms.instance":"Instance Manager Permissions","perms.instance.read":"Read Accessible Instances","perms.instance.read.desc":"Ability to list and view instances the user is allowed access to. WARNING: Users who know the instance ID can still use the API to edit it using other permissions even if they lack this one.","perms.instance.create":"Create Instances","perms.instance.create.warning":"The current user is not permitted to create instances","perms.instance.create.desc":"Ability to create new instances","perms.instance.rename":"Rename Instances","perms.instance.rename.desc":"Ability to rename instances","perms.instance.relocate":"Relocate Instances","perms.instance.relocate.desc":"Ablity to change the location of an instance on the file system","perms.instance.setonline":"Change Instance Online Status","perms.instance.setonline.desc":"Ability to set an instance as online or offline","perms.instance.delete":"Delete Instance","perms.instance.delete.desc":"Ablity to delete an instance","perms.instance.list":"Read All Instances","perms.instance.list.desc":"Ability to list and view all instances","perms.instance.setconfiguration":"Set Instance Configuration Mode","perms.instance.setconfiguration.desc":"Ability to set an instance\'s static file editing mode","perms.instance.setautoupdate":"Set Instance Autoupdate Interval","perms.instance.setautoupdate.desc":"Ability to set an instance\'s interval for automatic code updates","perms.instance.setchatbotlimit":"Set Instance Chatbot Limit","perms.instance.setchatbotlimit.desc":"Ability to change an instance\'s maximum amounts of bots","perms.instance.grantpermissions":"Grant All Permissions","perms.instance.grantpermissions.desc":"Ability to grant themselves all permissions on any instance","perms.instancepermissionset":"Instance Admin","perms.instancepermissionset.cantedit":"You require the \\"Write\\" instance permission set flag to change these values","perms.instancepermissionset.create":"Create new Instance Permission Sets","perms.instancepermissionset.create.desc":"Allows you to grant access to this instance to other TGS users/groups.","perms.instancepermissionset.read":"Read Instance Permission Sets","perms.instancepermissionset.read.desc":"Allows you to read the permissions in this instance of other TGS users/groups already registered.","perms.instancepermissionset.write":"Write Instance Permission Sets","perms.instancepermissionset.write.desc":"Allows you to change or delete the permissions in this instance of both you and other TGS users/groups already registered.","perms.repository":"Repository","perms.repository.cancelpendingchanges":"Cancel Pending Changes","perms.repository.cancelpendingchanges.desc":"Allows cancelling active repository jobs (Except clone jobs).","perms.repository.setorigin":"Clone Repository","perms.repository.setorigin.desc":"Allows cloning the repository from a remote if it doesn\'t exist.","perms.repository.setsha":"Checkout SHA","perms.repository.setsha.desc":"Allows checking out a specific commit by it\'s SHA.","perms.repository.mergepullrequest":"Create Test Merge","perms.repository.mergepullrequest.desc":"Allows test merging a commit request from the remote.","perms.repository.updatebranch":"Update Branch","perms.repository.updatebranch.desc":"Allows hard reset and merge updates to the repository\'s current branch.","perms.repository.changecommitter":"Change Committer","perms.repository.changecommitter.desc":"Allows changing the username git commits are written as.","perms.repository.changetestmergecommits":"Change Remote Test Merge Settings","perms.repository.changetestmergecommits.desc":"Allows enabling/disabling the settings for pushing test merge commits to the remote in a temporary branch, posting comments when test merges occur, and creating GitHub deployments.","perms.repository.changecredentials":"Change Credentials","perms.repository.changecredentials.desc":"Allows changing the credentials used to access the repository.","perms.repository.setreference":"Checkout Branch/Tag","perms.repository.setreference.desc":"Allows checking out a specific branch/tag by it\'s name.","perms.repository.read":"Read Status Information","perms.repository.read.desc":"Allows reading repository status information (HEAD/Committer/Active Test Merges/etc...).","perms.repository.changeautoupdatesettings":"Change Auto Update Test Merge Settings","perms.repository.changeautoupdatesettings.desc":"Allows changing the setting to preserve test merges while updating a branch or synchronize with the remote.","perms.repository.delete":"Delete Repository","perms.repository.delete.desc":"Allows deleting the cloned repository from disk in order to clone another one.","perms.repository.cancelclone":"Cancel Clone","perms.repository.cancelclone.desc":"Allows cancelling active repository clone jobs.","perms.repository.changesubmoduleupdate":"Change Submodule Update","perms.repository.changesubmoduleupdate.desc":"Allows changing the setting to update submodules with branches.","perms.engine":"Engine","perms.engine.readactive":"Read Active Version","perms.engine.readactive.desc":"Allows reading the active engine version.","perms.engine.listinstalled":"List Installed Versions","perms.engine.listinstalled.desc":"Allows reading all installed engine versions.","perms.engine.installofficialorchangeactivebyondversion":"Activate/Install BYOND Version","perms.engine.installofficialorchangeactivebyondversion.desc":"Allows installation and activation of BYOND versions downloaded from the official website. Pre-installed custom versions may also be activated","perms.engine.installofficialorchangeactiveopendreamversion":"Activate/Install OpenDream Version","perms.engine.installofficialorchangeactiveopendreamversion.desc":"Allows installation and activation of OpenDream versions downloaded from the configured git. Pre-installed custom versions may also be activated","perms.engine.cancelinstall":"Cancel Install Job","perms.engine.cancelinstall.desc":"Allows cancelling install jobs.","perms.engine.installcustombyondversion":"Install Custom BYOND Version","perms.engine.installcustombyondversion.desc":"Allows installing a BYOND version uploaded from a zip file.","perms.engine.installcustomopendreamversion":"Install Custom OpenDream Version","perms.engine.installcustomopendreamversion.desc":"Allows installing an OpenDream version uploaded from a zip file.","perms.engine.deleteinstall":"Delete Engine Version","perms.dreammaker":"Deployment","perms.dreammaker.read":"Read Deployment Settings","perms.dreammaker.read.desc":"Allows reading information about the deployment settings.","perms.dreammaker.compile":"Create Deployment","perms.dreammaker.compile.desc":"Allows starting new deployment jobs.","perms.dreammaker.cancelcompile":"Cancel Deployment","perms.dreammaker.cancelcompile.desc":"Allows cancelling deployment jobs.","perms.dreammaker.setdme":"Set .dme Path","perms.dreammaker.setdme.desc":"Allows overriding TGS\' automatic .dme detection in favor of using a different .dme.","perms.dreammaker.setapivalidationport":"Set Validation Port","perms.dreammaker.setapivalidationport.desc":"Allows changing the port used by DreamDaemon to check for the presence of the DMAPI while deploying.","perms.dreammaker.compilejobs":"Read Compile Jobs","perms.dreammaker.compilejobs.desc":"Allows reading information about the current and past deployments.","perms.dreammaker.setsecuritylevel":"Set Validation Security Level","perms.dreammaker.setsecuritylevel.desc":"Allows changing the BYOND security level used to validate the DMAPI.","perms.dreammaker.setapivalidationrequirement":"Set Validation Requirement","perms.dreammaker.setapivalidationrequirement.desc":"Allows setting whether or not the DMAPI is required for successful deployments.","perms.dreammaker.settimeout":"Set Deployment Timeout","perms.dreammaker.settimeout.desc":"Allows setting the amount of time allowed to pass before a deployment job is automatically cancelled.","perms.dreammaker.setcompilerarguments":"Set Additional Compiler Arguments","perms.dreammaker.setcompilerarguments.desc":"Allows setting additional compiler arguments to be passed on the command line.","perms.dreamdaemon":"Server","perms.dreamdaemon.readrevision":"View Current Compile Jobs","perms.dreamdaemon.readrevision.desc":"Allows reading information about the latest and staged compile jobs.","perms.dreamdaemon.setlogoutput":"Set DreamDaemon Output Logging","perms.dreamdaemon.setlogoutput.desc":"Allows setting whether or not DreamDaemon will log it\'s output to the Diagnostics folder.","perms.dreamdaemon.setautostart":"Set Autostart","perms.dreamdaemon.setautostart.desc":"Allows setting whether or not DreamDaemon will start automatically when the instance comes online.","perms.dreamdaemon.setport":"Set Game Port","perms.dreamdaemon.setport.desc":"Allows setting the port DreamDaemon listens on.","perms.dreamdaemon.setsecurity":"Set Security Level","perms.dreamdaemon.setsecurity.desc":"Allows setting the BYOND security level to run the game at (Note: a higher DMAPI setting stored in a compile job can override this).","perms.dreamdaemon.readmetadata":"Read Settings","perms.dreamdaemon.readmetadata.desc":"Allows reading the current DreamDaemon settings.","perms.dreamdaemon.setwebclient":"Set Webclient","perms.dreamdaemon.setwebclient.desc":"Allows enabling/disabling use of the BYOND webclient.","perms.dreamdaemon.softrestart":"Graceful Restart","perms.dreamdaemon.softrestart.desc":"Allows requesting the server is gracefully restarted (Process restart on world.Reboot()).","perms.dreamdaemon.softshutdown":"Graceful Shutdown","perms.dreamdaemon.softshutdown.desc":"Allows requesting the server is gracefully shutdown (Process terminiation on world.Reboot()).","perms.dreamdaemon.restart":"Hard Restart","perms.dreamdaemon.restart.desc":"Allows immediately restarting the DreamDaemon process.","perms.dreamdaemon.shutdown":"Hard Shutdown","perms.dreamdaemon.shutdown.desc":"Allows immediately terminating the DreamDaemon process.","perms.dreamdaemon.start":"Server Launch","perms.dreamdaemon.start.desc":"Allows launching DreamDaemon from a shutdown state.","perms.dreamdaemon.setstartuptimeout":"Set Startup Timeout","perms.dreamdaemon.setstartuptimeout.desc":"Allows changing the idle/DMAPI detection timeout for starting new servers before it\'s considered a failure. This also applies to DreamDaemon instances used during deployments.","perms.dreamdaemon.sethealthcheckinterval":"Set Health Check Interval","perms.dreamdaemon.sethealthcheckinterval.desc":"Allows changing the interval at which health check Topics are sent.","perms.dreamdaemon.createdump":"Create Process Dump","perms.dreamdaemon.createdump.desc":"Allows creating dump files of DreamDaemon while it is running.","perms.dreamdaemon.settopictimeout":"Set Topic Timeout","perms.dreamdaemon.settopictimeout.desc":"Allows setting the timeout interval for sending Topics to the server before being considered a failure.","perms.dreamdaemon.setadditionalparameters":"Set Additional Parameters","perms.dreamdaemon.setadditionalparameters.desc":"Allows adding additional DreamDaemon launch parameters (Not command line arguments).","perms.dreamdaemon.setvisibility":"Set Visibility","perms.dreamdaemon.setvisibility.desc":"Allows changing the DreamDaemon visibility setting.","perms.dreamdaemon.setprofiler":"Set Profiler","perms.dreamdaemon.setprofiler.desc":"Allows setting the `-profile` command line option.","perms.dreamdaemon.setmapthreads":"Set Map Threads","perms.dreamdaemon.setmapthreads.desc":"Allows setting the `-map-threads` command line option.","perms.dreamdaemon.broadcastmessage":"Send Broadcast Messages","perms.dreamdaemon.broadcastmessage.desc":"Allows sending arbitrary messages from TGS clients to the running game server for broadcasting to players.","perms.dreamdaemon.setminidumps":"Change Core Dump Type","perms.dreamdaemon.setminidumps.desc":"Allows switching between creating minidumps and full core dumps.","perms.chatbots":"Chat Bots","perms.chatbots.writeenabled":"Set Enabled","perms.chatbots.writeenabled.desc":"Allows activation and deactivation of chat bots.","perms.chatbots.writeprovider":"Set Provider","perms.chatbots.writeprovider.desc":"Allows changing the chat provider of chat bots.","perms.chatbots.writechannels":"Set Channels","perms.chatbots.writechannels.desc":"Allows changing the channels chat bots are connected to.","perms.chatbots.writeconnectionstring":"Set Connection String","perms.chatbots.writeconnectionstring.desc":"Allows adjusting the TGS connection string of chat bots.","perms.chatbots.readconnectionstring":"Read Connection String","perms.chatbots.readconnectionstring.desc":"Allows reading the TGS connection string of chat bots. May contain sensitive data","perms.chatbots.read":"Read","perms.chatbots.read.desc":"Allows reading all chat bot information aside from their connection strings.","perms.chatbots.create":"Create","perms.chatbots.create.desc":"Allows the creation of bew chat bots.","perms.chatbots.delete":"Delete","perms.chatbots.delete.desc":"Allows the deletion of chat bots.","perms.chatbots.writename":"Set Name","perms.chatbots.writename.desc":"Allows changing the name of chat bots.","perms.chatbots.writereconnectioninterval":"Set Reconnection Interval","perms.chatbots.writereconnectioninterval.desc":"Allows changing the interval at which chat bots that have lost connection to their providers will attempt to reconnect.","perms.chatbots.writechannellimit":"Set Channel Limit","perms.chatbots.writechannellimit.desc":"Allows changing the channel limit of chat bots.","perms.configuration":"Files & Scripts","perms.configuration.read":"Read Files","perms.configuration.read.desc":"Allows downloading configuration files where permitted.","perms.configuration.write":"Modify Files","perms.configuration.write.desc":"Allows uploading, overwriting, and deleting configuration files where permitted.","perms.configuration.list":"Traverse Directories","perms.configuration.list.desc":"Allows listing the contents of configuration directories where permitted.","perms.configuration.delete":"Delete Directories","perms.configuration.delete.desc":"Allows deleting empty configuration directories where permitted.","perms.group":"Group","perms.group.create":"Create Group","perms.group.warning":"This user is linked to the \\"{group}\\" group. Any change to the permissions will be applied to the group.","perms.group.delete.tooltip":"This group contains more than 0 users. Remove all users before deleting the group.","perms.group.rename.tooltip":"Rename","perms.group.none":"No group","perms.group.current":"Current Group: ","perms.group.cantlist":"You lack the permission to list all users. You need it to be able to list all groups","config.githubtoken":"Github token","config.githubtoken.desc":"You can supply a private authorization token for github to bypass some rate limiting on the github API.","config.apipath":"TGS API Path","config.apipath.desc":"Sets the API client server\'s path.","config.jobpollinactive":"Inactive job poll delay (s)","config.jobpollinactive.desc":"After how many seconds should we check for new jobs if we dont have any active jobs","config.jobpollactive":"Active job poll delay (s)","config.jobpollactive.desc":"After how many seconds should we check for new jobs if we know about a job","config.jobswidgetdisplay":"Jobs Widget Display","config.jobswidgetdisplay.desc":"Display mode for the instance jobs widget","config.jobswidgetdisplay.enum.always":"Always display","config.jobswidgetdisplay.enum.auto":"Display when there are jobs","config.jobswidgetdisplay.enum.never":"Never display","config.instanceprobetimer":"Instance permission poll delay","config.instanceprobetimer.desc":"After how many seconds should we update the list of instances","config.itemsperpage":"Items Per Page","config.itemsperpage.desc":"This allows you to configure the amount of items shown at once in lists. Note that not all lists support this option yet.","config.instanceeditsidebar":"Instance Edit Sidebar","config.instanceeditsidebar.desc":"This controls how the left sidebar menu acts in the instance edit page.","config.instanceeditsidebar.enum.auto":"Expand on hover","config.instanceeditsidebar.enum.collapse":"Always collapsed","config.instanceeditsidebar.enum.expand":"Always expanded","config.showjson":"Show JSON objects","config.showjson.desc":"Most pages will display the underlying json data if enabled.","config.manualpr":"Show manual PR entry on repository page","config.manualpr.desc":"Controls whether or not an input box is displayed to manually testmerge a pr based on its PR number. This option is ignored and the manual PR entry box is always displayed when using a repository hosted on GitLab.","config.manualreset":"Show force reset switch on repository page","config.manualreset.desc":"Controls whether or not an input box is displayed to force a repository reset. This option is ignored and the manual PR entry box is always displayed when using a repository hosted on GitLab.","config.restjobs2":"Force HTTP Polling for Job Updates","config.restjobs2.desc":"Workaround for if SignalR job updates are failing","loading.loading":"Loading...","loading.login":"Logging in...","loading.page":"Loading page: ","loading.page.notfound":"Loading page: NotFound","loading.perms":"Updating permissions...","loading.app":"Loading app...","loading.admin":"Loading admin info...","loading.version":"Loading versions...","loading.updating":"Updating server...","loading.logs":"Loading Log(s)...","loading.info":"Loading Information...","loading.serverinfo":"Loading Server Information...","loading.passwd":"Changing password...","loading.userlist":"Loading user list...","loading.user.load":"Loading user information...","loading.user.save":"Saving user information...","loading.user.create":"Creating user...","loading.instance.list":"Loading instance list...","loading.instance.move":"Relocating instance...","loading.instance.files":"Performing file operation...","loading.instance.jobs.list":"Loading job list...","loading.instance":"Loading instance...","loading.instance.server":"Loading watchdog information...","loading.repo.cloning":"Cloning repository...","loading.repo.prs":"Loading PRs...","loading.repo.commits":"Loading Commits...","loading.repo.busy":"The repository is currently busy...","loading.deployments":"Loading DreamMaker Settings...","loading.compile_jobs":"Loading Compile Jobs...","loading.chat":"Loading Chat Bots...","loading.byond":"Loading BYOND Version Information...","loading.instance.perms":"Loading Instance Permissions...","fields.instance.name":"Instance Name","fields.instance.path":"Path on disk","fields.instance.chatbotlimit":"Max chatbots","fields.instance.filemode":"Static File Edit Mode","fields.instance.autoupdate":"Autoupdate Interval in minutes (0 to disable)","fields.instance.chat.channel.admin":"Admin Channel","fields.instance.chat.channel.admin.tip":"This channel can be used to receive messages and issue TGS chat commands designated as admin only","fields.instance.chat.channel.discord":"Channel ID","fields.instance.chat.channel.discord.tip":"Right click a channel with developer mode enabled to see the option to copy its ID","fields.instance.chat.channel.irc":"IRC Channel","fields.instance.chat.channel.irc.tip":"Include the \'#\'","fields.instance.chat.channel.tag":"DMAPI Tag","fields.instance.chat.channel.tag.tip":"A string associated with this channel in the DMAPI","fields.instance.chat.channel.updates":"Deployments Channel","fields.instance.chat.channel.updates.tip":"This channel will receive TGS deployment messages","fields.instance.chat.channel.system":"System Channel","fields.instance.chat.channel.system.tip":"This channel will receive TGS system messages (Restarts and updates)","fields.instance.chat.channel.watchdog":"Watchdog Channel","fields.instance.chat.channel.watchdog.tip":"This channel will receive live updates as to the state of the active DreamDaemon process","fields.instance.chat.create.channel":"Add Channel","fields.instance.chat.create.discord.token":"Bot Token","fields.instance.chat.create.discord.token.tip":"Bot access token retrieved from Discord developers portal","fields.instance.chat.create.discord.output":"Show Deployment Messages","fields.instance.chat.create.discord.output.tip":"When messages from deployment jobs will be shown in Watchdog-type channels","fields.instance.chat.create.discord.based.tip":"Know your meme","fields.instance.chat.create.discord.branding":"Deployment Embed Branding","fields.instance.chat.create.discord.branding.tip":"If the tgstation-server logo, name, and link to repo are shown at the top of deployment embeds","fields.instance.chat.create.irc.address":"Server Address","fields.instance.chat.create.irc.address.tip":"IP/Domain only","fields.instance.chat.create.irc.port":"Server Port","fields.instance.chat.create.irc.nick":"Bot Nickname","fields.instance.chat.create.irc.pass":"Server Password","fields.instance.chat.create.irc.pass.tip":"Setting a password on the server for your IRC bot is HIGHLY recommended","fields.instance.chat.create.irc.passtype":"Server Password Type","fields.instance.chat.create.irc.ssl":"Connect with SSL","fields.instance.chat.create.irc.ssl.tip":"Note: This is dependent on the IRC server\'s configuration","fields.instance.chat.create.save":"Create Chat Bot","fields.instance.chat.edit.connection":"Connection String","fields.instance.chat.edit.connection.tip":"Contains sensitive information. Consider remaking the bot instead of modifying this due to the internal formatting","fields.instance.chat.edit.connection.unloaded":"Not Loaded. Edit available","fields.instance.chat.edit.connection.deny":"Lacking read permission. Edit may still available","fields.instance.chat.enabled":"Enabled","fields.instance.chat.enabled.tip":"If the bot is to be online with the instance","fields.instance.chat.limit":"Channel Limit","fields.instance.chat.limit.tip":"Maximum number of channels this bot can interact with","fields.instance.chat.provider":"Chat Service Provider","fields.instance.chat.reconnect":"Reconnection Interval (Minutes)","fields.instance.chat.reconnect.tip":"The period at which the bot will attempt to reconnect to the chat service if unexpectedly disconnected","fields.instance.chat.name":"Chat Bot Name","fields.instance.chat.name.tip":"Internal name of the chat bot","fields.instance.filemode.Disallowed":"No File Management.","fields.instance.filemode.HostWrite":"Authorized users can edit any file.","fields.instance.filemode.SystemIdentityWrite":"Users using a system identity can edit files their user has access to.","fields.instance.files.create":"Create Directory or Upload File","fields.instance.files.create.directory":"Create directory instead of file","fields.instance.files.create.name":"File system entry name","fields.instance.files.create.name.tip":"The name of the file or directory being created (extension included for files)","fields.instance.perms.owner":"Editing Instance Permissions For","fields.instance.perms.owner.switch":"Switch","fields.instance.watchdog.autostart":"Start server with instance","fields.instance.watchdog.autostartprofiler":"Start BYOND profiler automatically","fields.instance.watchdog.allowwebclient":"Allow BYOND web client connections","fields.instance.watchdog.logoutput":"Log DreamDaemon Output","fields.instance.watchdog.minidumps":"Use minidumps instead of full core dumps","fields.instance.watchdog.visibility":"BYOND hub visibility","fields.instance.watchdog.visibility.Public":"Public","fields.instance.watchdog.visibility.Private":"Private","fields.instance.watchdog.visibility.Invisible":"Invisible","fields.instance.watchdog.securitylevel":"BYOND security level","fields.instance.watchdog.securitylevel.Trusted":"Trusted","fields.instance.watchdog.securitylevel.Safe":"Safe","fields.instance.watchdog.securitylevel.Ultrasafe":"Ultra-Safe","fields.instance.watchdog.port":"Network port","fields.instance.watchdog.timeout.startup":"Startup timeout (seconds)","fields.instance.watchdog.timeout.topic":"Topic timeout (milliseconds)","fields.instance.watchdog.healthcheck":"Health Check Timeout (seconds)","fields.instance.watchdog.dumpOnHealthCheckRestart":"Create process dump on health check fail restart","fields.instance.watchdog.additionalparams":"Additional command line parameters","fields.instance.watchdog.mapthreads":"Map Threads Count (0 for default)","fields.instance.watchdog.broadcast":"Broadcast Message","fields.instance.watchdog.broadcast.desc":"A message to broadcast to players using the DMAPI. Requires the server be running with an interop version >=5.7.0","fields.instance.repository.origincheckoutsha":"Origin SHA","fields.instance.repository.origincheckoutsha.desc":"SHA of the origin commit","fields.instance.repository.checkoutsha":"Checkout SHA","fields.instance.repository.checkoutsha.desc":"SHA of the commit to checkout","fields.instance.repository.reference":"Reference","fields.instance.repository.reference.desc":"Set this to the branch, commit or tag you wish to track","fields.instance.repository.committerName":"Committer Name","fields.instance.repository.committerEmail":"Committer Email","fields.instance.repository.accessUser":"Access Username","fields.instance.repository.accessUser.desc":"These credentials will be used when cloning the repository or performing authenticated actions","fields.instance.repository.accessToken":"New Access Password","fields.instance.repository.accessToken.desc":"For github, this will be a PAT(Private Authentication Token), for other providers, this will be a password","fields.instance.repository.clearAccessToken":"Clear Access Credentials","fields.instance.repository.pushTestMergeCommits":"Push Test Merge Commits","fields.instance.repository.pushTestMergeCommits.desc":"This will push commits created by test merges to a temporary branch on the remote. Requires access credentials.","fields.instance.repository.createGitHubDeployments":"Create GitHub Deployments","fields.instance.repository.createGitHubDeployments.desc":"Requires access credentials","fields.instance.repository.showTestMergeCommitters":"Show test merge commiters in public metadata","fields.instance.repository.showTestMergeCommitters.desc":"Shows who test merged a PR. This only applies to future commits.","fields.instance.repository.autoUpdatesKeepTestMerges":"Preserve test merges when auto updating","fields.instance.repository.autoUpdatesKeepTestMerges.desc":"If enabled, auto updates may fail if a merge conflict occurs.","fields.instance.repository.autoUpdatesSynchronize":"Push new commits to origin during auto-update","fields.instance.repository.autoUpdatesSynchronize.desc":"Used for example, with changelog scripts depending on the setup","fields.instance.repository.postTestMergeComment":"Post comment when test merge is deployed","fields.instance.repository.postTestMergeComment.desc":"This will post a github comment each time a test merge is deployed or updated","fields.instance.repository.updateSubmodules":"Update submodules automatically","fields.instance.repository.updateSubmodules.desc":"Submodules will be updated automatically when resetting, checking out or adding a test merge. This is not recursive","fields.instance.repository.url":"Remote URL","fields.instance.repository.ref":"Remote reference (branch)","fields.instance.repository.gituser":"Git access username","fields.instance.repository.gitpassword":"Git access password","fields.instance.repository.enablesubmodules":"Enable submodules","fields.instance.deploy.projectname":"DME name (blank for auto)","fields.instance.deploy.projectname.desc":"This can also be a relative path and shouldn\'t include the file extension","fields.instance.deploy.compilerargs":"Additional Compiler Arguments","fields.instance.deploy.compilerargs.desc":"These are added to compiler command lines right before the path to the .dme","fields.instance.deploy.timeout":"Job timeout (in minutes)","fields.instance.deploy.timeout.desc":"Time before a compile job is abandonned and cancelled","fields.instance.deploy.apiport":"DMAPI port (0 for auto)","fields.instance.deploy.apiport.desc":"This port should not be public","fields.instance.deploy.seclevel":"DMAPI validation security level","fields.instance.deploy.seclevel.Trusted":"Trusted","fields.instance.deploy.seclevel.Safe":"Safe","fields.instance.deploy.seclevel.Ultrasafe":"Ultra-Safe","fields.instance.deploy.seclevel.desc":"This is only used for the DMAPI validation","fields.instance.deploy.validateapi":"Validate DMAPI","fields.instance.deploy.validateapi.desc":"This will check that the DMAPI initializes successfully without any runtimes","warning.screensize.header":"Screen size warning","warning.screensize":"The TGS webpanel does not guarentee support for viewports with a width of under 992px."}')}}]); \ No newline at end of file diff --git a/webpanel/5.7.0/422.de987a83a6a343eed949.bundle.js b/webpanel/5.7.0/422.de987a83a6a343eed949.bundle.js deleted file mode 100644 index b5bea80c..00000000 --- a/webpanel/5.7.0/422.de987a83a6a343eed949.bundle.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunktgstation_server_control_panel=self.webpackChunktgstation_server_control_panel||[]).push([[422],{2422:function(e){e.exports=JSON.parse('{"routes.home":"Home","routes.user_manager":"User Manager","routes.login":"Log In","routes.admin":"Administration","routes.admin.update":"TGS Update","routes.admin.logs":"TGS Logs","routes.config":"Webpanel Config","routes.passwd":"Change Password","routes.usermanager":"Users","routes.useredit":"User Editor","routes.usercreate":"Create User","routes.instancelist":"Instances","routes.instancecreate":"Add New Instance","routes.instanceedit":"Edit Instance","routes.instancejobs":"Jobs","routes.setup":"PostInstall Setup","routes.info":"Info","login.header":"Login to Continue","login.type.generic":"Password Login","login.type.oauth":"OAuth Login","login.oauth":"Sign in with {provider}","login.submit":"Login","login.password":"Password","login.password.repeat":"Confirm Password","login.password.repeat.match":"Passwords do not match!","login.password.repeat.short":"Password is too short! It must have a length of atleast ","login.title":"Login","login.username":"Username","navbar.home":"Home","navbar.purgecache":"Purge Client Cache","navbar.refresh":"Refresh","navbar.logout":"Logout","navbar.update":"Server Update Available","app.job.started":"Started: ","app.job.startedby":"Started By: ","app.job.completed":"Finished: ","app.job.cancelled":"Cancelled: ","app.job.cancelledby":"Cancelled By: ","error.http.access_denied.desc":"Access to this resource is denied","error.unhandled_response.desc":"The application received an unexpected response","error.unhandled_global_response.desc":"The application received an unexpected global response","error.login.no_creds.desc":"Attempted to login without any credentials","error.login.bad_oauth":"An error occurred while logging in using OAuth","error.login.bad_user_pass.desc":"Invalid credentials!","error.login.user_disabled.desc":"This user account is disabled","error.user.no_sys_ident.desc":"This system user was not found","error.user.not_found.desc":"This TGS user was not found","error.http.bad_request":"Bad Request","error.http.data_integrity":"A data integrity check failed while performing the operation","error.http.api_mismatch":"API version mismatch","error.http.server_error":"BUG: Server error","error.http.unimplemented":"This feature is unimplemented.","error.http.server_not_ready":"The server is still starting/stoping!","error.http.access_denied":"Access denied","error.http.not_acceptable":"BUG: The server has found the request to be unacceptable.","error.axios":"Axios error","error.unhandled_response":"The application received an unexpected response","error.unhandled_global_response":"The application received an unexpected global response","error.login.no_creds":"Attempted to login without any credentials","error.login.bad_user_pass":"Invalid credentials!","error.login.user_disabled":"This user account is disabled","error.login.rate_limit":"Failed to login using external provider due to rate limiting. Please try again later","error.user.no_sys_ident":"This system user was not found","error.user.not_found":"This TGS user was not found","error.group.not_found":"The requested group cannot be found.","error.group.not_empty":"The requested group cannot be deleted as it contains users.","error.admin.rate":"The server has exceeded github\'s rate limiting. Please try again later.","error.admin.error":"The server has ran into an error while using github\'s API.","error.admin.watchdog.avail":"This operation is unavailable due to the launch configuration of TGS.","error.admin.update.notfound":"This TGS version does not seem to exist.","error.admin.logs.io":"An IO error occured while processing logs","error.somethingwentwrong":"Uh oh.... Something went wrong!","error.notfound":"This page has not been found!","error.github":"An error occured while interacting with the Github API","error.app":"An error occured in the application","error.app.undefined":"A \\"Ghost\\" error occured in the application","error.app.default_creds":"You are using the default TGS credentials. Please click \'Change Password\' at earliest convenience!","error.job.not_found":"The specified job could not be found","error.job.complete":"Unable to delete the job, perhaps it already completed","error.transfer.not_available":"Unable to transfer file as it is no longer or never was valid","error.transfer.upload_failed":"An error occured while uploading a file","error.no_db_entity":"The database entity for the requested instance could not be retrieved. The instance was likely detached","error.api.empty":"No description available","error.no_apipath":"No API path set, set an API path on the configuration page.","error.compile_job_not_found":"Compile job not found.","error.bad_tgs_yml_version":"Incorrect .tgs.yml version. Only version 1 is supported.","error.bad_yml":"Malformed .yml.","error.bad_channels_json":"Malformed channels JSON.","error.no_engine_version":"The target engine version does not exist on the server.","error.bad_hub_connection":"Lost connection to the SignalR hub.","generic.save":"Save","generic.savetab":"Save Tab","generic.saveall":"Save All","generic.details":"Details","generic.downloading":"Downloading \\"{file}\\"...","generic.downloaded":"Downloaded \\"{file}\\"","generic.errordetails":"Error Details ({info})","generic.debugwarn":"Be careful to censor out any credentials or tokens when copying errors!","generic.close":"Close","generic.clone":"Clone","generic.goback":"Go Back","generic.accessdenied":"This user does not have access to this page.","generic.persist":"Persist","generic.continue":"Continue","generic.view":"View","generic.download":"Download","generic.name":"Name","generic.datetime":"Date/Time","generic.entry":"Entry","generic.action":"Action","generic.createdby":"Created By","generic.created":"Created","generic.disabled":"Disabled","generic.enabled":"Enabled","generic.grouped":"Grouped","generic.tgs":"TGS","generic.group":"Group","generic.groupid":"Group ID {id}","generic.info":"Info","generic.system.short":"SYSTEM","generic.edit":"Edit","generic.access":"Access","generic.systemidentifier":"System Identifier","generic.appname":"tgstation-server","generic.userid":"User ID ","generic.numusers":"{count} Users","generic.setall":"Set all","generic.true":"True","generic.false":"False","generic.reset":"Reset","generic.disable":"Disable","generic.enable":"Enable","generic.configmode":"Configuration Mode","generic.online":"Online","generic.offline":"Offline","generic.path":"Path","generic.select":"Select","generic.instance":"Instance","generic.goto.title":"Go to page","generic.goto":"Goto","generic.wip":"Work In Progress!","generic.readonly":"(Read-Only)","generic.invalid_form":"This form contains invalid values!","generic.no_perm":"You do not have the permission to do this","generic.wip.desc":"The TGS webpanel is still a work in progress. The feature you are trying to use is not yet available. Until it becomes available, please use the TGS desktop client at ","generic.assert.noinstance":"No instance. Perhaps an error occured.","generic.assert.nouser":"No user. Perhaps an error occured.","generic.assert.nopermissionset":"No permission set. Perhaps an error occured.","generic.latest":"Latest","generic.testmerged":"Testmerged","generic.commit":"Commit","generic.areyousure":"Are you sure?","generic.cancel":"Cancel","generic.not_applicable":"N/A","view.setup.navigationblock":"Navigation has been disabled for the duration of the setup.","view.setup.title":"Step By Step Setup Wizard","view.setup.quit":"Quit Setup","view.setup.quitconfirm":"Are you sure you want to exit the setup? You will not be able to return.","view.setup.disableadmin":"Disable default Admin account","view.setup.nextpage":"Next Page","view.setup.step.1":"Step 1. Create yourself a user account","view.setup.step.2":"Step 2. Login using your new user account","view.setup.step.3":"Step 3. Disable the default Admin account","view.setup.step.4":"Step 4. Configure clientside settings (Optional)","view.setup.step.5":"Setup Complete!","view.admin.hostos":"Host Machine OS: ","view.admin.remote":"Remote repository: ","view.admin.version.current":"Current Version: ","view.admin.version.latest":"Latest Version: ","view.admin.reboot.button":"Restart TGS","view.admin.reboot.modal.title":"Confirmation","view.admin.reboot.modal.body":"Are you sure you wish to restart TGS?","view.admin.update.button":"Update TGS","view.admin.update.selectversion":"Select Version","view.admin.update.latest":" (Latest)","view.admin.update.current":" (Current)","view.admin.update.releasenotes":"Release Notes","view.admin.update.wait":"Please take the time to read the release notes before proceeding","view.admin.update.showall":"Show all versions","view.admin.update.selectversion.deny":"You do not have permission to update to a GitHub version","view.admin.update.upload":"Upload Update Package","view.admin.update.upload.deny":"You do not have permission to update using uploaded packages","view.admin.update.major_warn.title":"WARNING: Attempting Major TGS Version Change","view.admin.update.major_warn.body":"You are attempting to switch the major version of TGS from {currentMajor} to {targetMajor}! READ THE RELEASE NOTES CAREFULLY! Most TGS major upgrades constitute a change in the required .NET runtime. If this runtime is not installed PRIOR to updating, TGS will FAIL to restart after upgrading until it is! There also may be REQUIRED configuration changes that, if not made, may also result in a failure to restart!","view.admin.logs.button":"TGS Logs","view.user.list.cantlist":"This user does not have the permission to list users, only the current user is listed/editable.","view.user.edit.cantedit":"This user does not have the permission to edit users.","view.user.edit.oauth.connections":"OAuth Connections","view.user.edit.oauth.current":"OAuth 2.0 Connections","view.user.edit.oauth.add":"Add Connection","view.user.edit.oauth.id":"Service User ID:","view.user.edit.oauth.provider":"Provider","view.user.edit.oauth.provider.discord":"Discord","view.user.edit.oauth.provider.github":"GitHub","view.user.edit.oauth.provider.tgforums":"/tg/ Forums","view.user.edit.oauth.provider.keycloak":"Keycloak","view.user.edit.oauth.provider.invisioncommunity":"Invision Community","view.user.passwd.title":"Editing password for ","view.user.create.tgs":"Create user with TGS identifier","view.user.create.sys":"Create user with system identifier","view.utils.deployment_viewer.dmapi_outdated":"Your codebase\'s DMAPI interop version ({codebase}) is not present or less than tgstation-server\'s version ({tgs}). Please update your codebase with the latest TGS DMAPI library for full functionality. Click here to go to the latest TGS DMAPI release.","view.utils.deployment_viewer.no_jobs":"No deployments have been created!","view.utils.deployment_viewer.test_merges_hint.show":"Show Test Merges","view.utils.deployment_viewer.test_merges_hint.hide":"Hide Test Merges","view.utils.deployment_viewer.table.id":"Id","view.utils.deployment_viewer.table.byond":"Engine Version","view.utils.deployment_viewer.table.started_at":"Started At","view.utils.deployment_viewer.table.completed_at":"Completed At","view.utils.deployment_viewer.table.started_by":"Started By","view.utils.deployment_viewer.table.project":"Project Name","view.utils.deployment_viewer.table.pr.number":"Test Merge #","view.utils.deployment_viewer.table.pr.title":"Title","view.utils.deployment_viewer.table.pr.merged_by":"Merged By","view.utils.deployment_viewer.table.pr.merged_at":"Merged At","view.utils.deployment_viewer.table.pr.comment":"Comment","view.utils.deployment_viewer.table.revision":"SHA","view.utils.deployment_viewer.table.origin":"Origin SHA","view.utils.deployment_viewer.table.security":"Minimum Security","view.utils.deployment_viewer.table.dmapi":"DMAPI Interop Version","view.instance.no_metadata":"You do not have the permission to read all settings, only editable fields will be shown. Said fields will only contain default values.","view.instance.no_compile_jobs":"You do not have the permission to view deployment information!","view.instanceedit.title":"Editing Instance {instancename} ({instanceid})","view.instanceedit.tabs.info":"Information","view.instanceedit.tabs.repository":"Repository","view.instanceedit.tabs.deployment":"Deployment","view.instanceedit.tabs.dreamdaemon":"Server","view.instanceedit.tabs.engine":"Engine","view.instanceedit.tabs.chatbots":"Chat Bots","view.instanceedit.tabs.files":"Files & Scripts","view.instanceedit.tabs.users":"Permissions","view.instanceedit.tabs.jobs":"Jobs History","view.instanceedit.tabs.config":"Config","view.instance.create.loading":"Creating Instance...","view.instance.create.title":"New Instance","view.instance.create.name":"Instance Name","view.instance.create.manual":"Manual Setup","view.instance.create.quick":"Quick Setup","view.instance.create.quick.active":"Performing Instance Quick Setup","view.instance.create.quick.stage.yml":"Downloading .tgs.yml...","view.instance.create.quick.stage.download_scripts":"Downloading script \\"{script}\\"...","view.instance.create.quick.stage.create_instance":"Creating instance...","view.instance.create.quick.stage.cloning":"Starting repository clone...","view.instance.create.quick.stage.byond":"Starting {version} BYOND install...","view.instance.create.quick.stage.settings":"Updating compiler/server settings...","view.instance.create.quick.stage.upload_scripts":"Uploading EventScript \\"{script}\\"...","view.instance.create.quick.stage.static":"Creating GameStaticFiles directory \\"{dir}\\"...","view.instance.create.quick.stage.static.transfer":"Transferring \\"{path}\\" from GitHub to GameStaticFiles/{targetPath}...","view.instance.create.quick.submit":"Start Quick Setup","view.instance.create.quick.notice":"Quick setup requires the following:{br}\\t- A GitHub hosted repository.{br}\\t- A codebase containing a .tgs.yml file in its root.{br}\\t- A GitHub personal access token with read access to the repository set in the webpanel settings.","view.instance.create.quick.warning":"Quick setup may install executable scripts or set the BYOND security level to a less secure value. Ensure you trust the codebase you are targeting.","view.instance.create.repo_owner":"GitHub Repository Owner","view.instance.create.repo_name":"GitHub Repository Name","view.instance.create.repo_branch":"GitHub Repository Reference (Optional)","view.instance.create.access_user":"GitHub Token Username (Optional)","view.instance.create.access_token":"GitHub Token (Optional)","view.instance.create.path":"Instance Path on Server","view.instance.create.path.prefix":"Prefix:","view.instance.create.submit":"Create Instance","view.instance.list.grant":"Grant yourself permission to access this instance","view.instance.list.grant.deny":"Requires the \\"Grant All Permissions\\" instance manager right","view.instance.list.title":"Instance List","view.instance.list.set.online":"Bring Online","view.instance.list.set.offline":"Take Offline","view.instance.configmode.0":"Disabled","view.instance.configmode.1":"Authorized users can read/write","view.instance.configmode.2":"Authorized users can read/write using their system user","view.instance.jobs.title":"Job list","view.instance.jobs.jobtotal":"{amount} jobs","view.instance.jobs.error":"An error occured","view.instance.jobs.clearfinished":"Clear finished jobs","view.instance.jobs.reconnect_in":"Attempting reconnect in {seconds}s...","view.instance.jobs.reconnect_now":"Attempting reconnection...","view.instance.jobs.reconnected_auth":"Reconnected, authenticating...","view.instance.moving":"[MOVING INSTANCE...]","view.instance.config.instancesettings":"Instance Settings","view.instance.config.instanceusers":"Instance Users","view.instance.config.chatbots":"Chat Bots","view.instance.engine":"Version Selector","view.instance.engine.add_byond":"Install new BYOND version","view.instance.engine.add_od":"Install new OpenDream version","view.instance.engine.upload":"Upload custom version","view.instance.engine.custom":"Uploaded from zip file","view.instance.engine.current_and_list_denied":"This user does not have the permission to access information about BYOND versions","view.instance.engine.list_denied":"This user does not have the permission to list all installed BYOND versions","view.instance.engine.current_denied":"This user does not have the permission to see the active BYOND version","view.instance.engine.current_version":"Active Version: {version}","view.instance.chat":"Chat Bots","view.instance.chat.create":"Add Bot","view.instance.chat.create.invalid.discord":"Invalid Discord channel ID!","view.instance.chat.create.invalid.irc":"Invalid IRC channel!","view.instance.chat.create.missing.address":"Missing IRC Server Address!","view.instance.chat.create.missing.channel":"Missing channel ID!","view.instance.chat.create.missing.name":"Missing Bot Name!","view.instance.chat.create.missing.nick":"Missing IRC Bot Nickname!","view.instance.chat.create.missing.token":"Missing Discord Bot Token!","view.instance.chat.create.channel":"Add Channel","view.instance.chat.delete":"Delete Bot","view.instance.chat.delete.deny":"You do not have permission to delete chat bots","view.instance.chat.delete.confirm":"Are you sure you want to delete chat bot \\"{botName}\\"?","view.instance.chat.delete.channel":"Delete Channel","view.instance.chat.delete.channel.confirm":"Are you sure you want to delete channel \\"{channelName}\\"?","view.instance.chat.limit":"Maximum of {max} chat bots reached!","view.instance.chat.limit.channels":"Maximum of {max} channels reached!","view.instance.chat.reload":"Load Connection String","view.instance.chat.reload.deny":"You do not have permission to view connection strings","view.instance.chat.select_item":"Select an Item on the Left","view.instance.chat.channels.export":"Export Channels to Clipboard","view.instance.chat.channels.import":"Import Channels from Clipboard","view.instance.chat.channels.deny":"You do not have permission to edit chat bot channels!","view.instance.files.create":"Create Item","view.instance.files.delete":"Delete File","view.instance.files.delete.confirm":"Are you sure you want to delete the file \\"{path}\\"","view.instance.files.delete.directory":"Delete Empty Directory","view.instance.files.delete.directory.populated":"Only empty directories may be deleted","view.instance.files.delete.directory.populated.unloaded":"Expand the directory to see if it contains files before deleting it","view.instance.files.delete.directory.confirm":"Are you sure you want to delete the empty directory \\"{directoryName}\\"?","view.instance.files.disallowed":"The instance settings prevent you from using the file browser","view.instance.files.disallowed.directory":"You do not have permission to view directory contents. The webpanel does not support file browsing without this permission.","view.instance.files.disallowed.directory.delete":"You do not have permission to delete directories","view.instance.files.disallowed.read_no_browse":"You do not have permission to browse nor download files.","view.instance.files.disallowed.read":"You do not have permission to download files.","view.instance.files.disallowed.write":"You do not have permission to create, modify, or delete files.","view.instance.files.download":"Download File","view.instance.files.download.directory":"Download Zip","view.instance.files.download.location":"Downloaded files will be saved in the OS\'s default download location","view.instance.files.file_browser":"File Browser","view.instance.files.load_more":"(Click to Load Directory)","view.instance.files.replace":"Overwrite with Uploaded File","view.instance.files.replace.stale":"Cannot replace file due to being unable to refresh its status!","view.instance.files.select_item":"Select an Item on the Left","view.instance.files.upload":"Upload and Overwrite File","view.instance.files.zip.confirm":"Are you sure you want to download the directory \\"{path}\\" as a zip? This can be a very intense operation for large folder structures and may not succeed.","view.instance.info":"Instance Metadata","view.instance.perms":"Instance Permissions","view.instance.perms.create":"Create Instance Permission Set","view.instance.perms.delete":"Delete Instance Permission Set","view.instance.perms.grant":"Grant Full Permissions","view.instance.perms.grant.desc":"You have access to grant yourself full permissions on this instance.","view.instance.perms.missing":"This permission set is not registered with the instance","view.instance.server.status":"Status: ","view.instance.server.status.Offline":"Offline","view.instance.server.status.Restoring":"Restoring","view.instance.server.status.Online":"Online","view.instance.server.status.DelayedRestart":"Delayed Restart","view.instance.server.status.undefined":"No permission","view.instance.server.settings":"Settings","view.instance.server.actions":"Actions","view.instance.server.start":"Start","view.instance.server.stop":"Stop","view.instance.server.restart":"Restart","view.instance.server.dump":"Dump Process","view.instance.server.deployment_info":"Deployment Information","view.instance.server.deployment_info.active":"Active Deployment","view.instance.server.deployment_info.staged":"Staged Deployment","view.instance.server.no_metadata_and_no_settings":"You do not have the permission to list or edit Dream Daemon settings for this instance.","view.instance.server.no_metadata_actions":"You do not have permission to fetch the status of the server, some actions may not work depending on the state of the server.","view.instance.server.no_metadata_graceful":"You do not have permission to fetch the current graceful action.","view.instance.server.no_graceful":"You do not have permission to get or set the current graceful action.","view.instance.server.no_actions":"You do not have permission to commit any actions.","view.instance.server.broadcast":"Broadcast","view.instance.server.prompt.restart":"Are you sure you wish to immediately restart the server?","view.instance.server.prompt.stop":"Are you sure you wish to immediately stop the server?","view.instance.graceful":"Graceful Action","view.instance.graceful.desc":"This action will be applied the next time the server restarts","view.instance.graceful.Restart":"Restart","view.instance.graceful.Stop":"Shutdown","view.instance.graceful.None":"None","view.instance.repo.canthookclone":"Unable to find clone job, refresh page when clone is complete. Please report this!","view.instance.repo.repoinfo":"Repository Information","view.instance.repo.reposettings":"Repository Settings","view.instance.repo.testmerges":"Test Merges","view.instance.repo.testmerges.badprovider":"Guided test merges are only supported on github","view.instance.repo.info.origin":"Origin URL","view.instance.repo.info.owner":"Repository Owner","view.instance.repo.info.name":"Repository Name","view.instance.repo.tm.by":"Testmerged by:","view.instance.repo.tm.comment":"Comment:","view.instance.repo.tm.commit":"Commit:","view.instance.repo.tm.modal.title":"Add or Update Testmerge","view.instance.repo.tm.modal.label":"Select Commit","view.instance.repo.tm.modal.comment":"Comment","view.instance.repo.tm.modal.tip":"Tip: When clicking the add or update button, hold shift to testmerge the latest commit bypassing this popup!","view.instance.repo.pending.title":"Pending Changes","view.instance.repo.pending.none":"No pending changes","view.instance.repo.pending.deploy":"Queue a deployment of repository code to the server","view.instance.repo.pending.reapply":"Retestmerge #{number} ({title}) at {commit}","view.instance.repo.pending.reset":"Reset repository to local tracked reference","view.instance.repo.pending.reset.nobranch":"Recheckout commit {commit}","view.instance.repo.pending.update":"Reset repository to remote tracked reference","view.instance.repo.pending.added":"Testmerge #{number} ({title}) at {commit}","view.instance.repo.pending.added.manual":"Manual testmerge of PR #{number}","view.instance.repo.pending.updated":"Update #{number} ({title}) to {commit}","view.instance.repo.pending.removed":"Removes #{number} ({title})","view.instance.repo.pending.renamed":"Change the comment on #{number}","view.instance.repo.update.remote":"Update to Remote","view.instance.repo.update.local":"Reset to Local Origin","view.instance.repo.update.local.tip":"Using this option may take you off the current repository reference. In order to update to latest, you will have to manually checkout said reference again.","view.instance.repo.update.none":"No Initial Change","view.instance.repo.reset":"Reset repository to origin","view.instance.repo.reset.desc":"This option will reset the repository to the tracked origin, updating the repository and clearing any test merged PRs","view.instance.repo.manual":"Manual Test Merge Entry","view.instance.repo.manual.desc":"Use this box to manually test merge a pull/merge request by entering its number and clicking \\"Add Test Merge\\"","view.instance.repo.addmanual":"Add Test Merge","view.instance.repo.testmergelabel":"Labelled","view.instance.repo.norepoinfo":"You lack the permission to display information about the repository","view.instance.repo.delete.title":"Delete Repository","view.instance.repo.delete.desc":"This will delete the local copy of the repository. Instance settings, code modifications, event scripts and static files will be preserved.","view.instance.repo.delete":"Delete Repo","view.instance.repo.clone":"Clone Remote Repository","view.instance.repo.noautomerge":"Automatic test merge management is unavailable due to missing permissions","view.instance.repo.deployAfter":"Compile & Deploy after changes","view.instance.repo.deployAfter.desc":"Compile and deploy the repository to the server after completing the test merge","view.instance.deploy.title":"Deployment Settings","view.instance.deploy.deploy":"Compile & Deploy repository","view.info.client":"Client Info","view.info.server":"Server Info","view.info.swarm":"Swarm Info","view.info.controller":"[CONTROLLER]","view.info.version":"Version: ","view.info.httpapiversion":"HTTP API Version: ","view.info.dmapiversion":"DM API Version: ","view.info.minpassword":"Minimum Password Length: ","view.info.instancelimit":"Instance Limit: ","view.info.userlimit":"User Limit: ","view.info.grouplimit":"Group Limit: ","view.info.oauth":"OAuth Support: ","view.meme_0":"Toxic Gamers\' Sanctuary","view.meme_1":"The Great Spaceman","view.meme_2":"The Griefers\' Stronghold","view.meme_3":"The Gourmet Spaceport","view.meme_4":"The Galactic Sh*tshow","view.meme_5":"The Grand Syndicate","view.meme_6":"The Giant Spacepickle","view.meme_7":"The Goofy Spacemen","view.meme_8":"The Godly Station","view.meme_9":"The Grumpy Scientists","view.meme_10":"Terrible Griefer Society","view.meme_11":"Taco-Generating Spacecraft","view.meme_12":"Thirsty Gamers\' Saloon","view.meme_13":"Titanic Gaming Server","view.meme_14":"Tragic Space Odyssey","view.meme_15":"Tinfoil Hat Guild","view.meme_16":"Turbocharged Game Station","view.meme_17":"Time-Traveling Game Show","view.meme_18":"Totally Gnarly Setup","view.meme_19":"Terrifying Ghost Ship","view.meme_20":"Tasty Grilled Sandwich","view.meme_21":"Treasure-Gathering Spacefarers","view.meme_22":"Tornado-Generating Storm","view.meme_23":"Twisted Gaming Society","view.meme_24":"Tgstation Sucks","view.meme_25":"Terra-Gov Server","view.report":"Report Issue","perms.admin":"Administration Permissions","perms.admin.writeusers":"Edit Users","perms.admin.writeusers.desc":"Ability to edit users, if View Users is granted, all users can be edited, otherwise, only the current user can be edited","perms.admin.restarthost":"Restart TGS","perms.admin.restarthost.desc":"Ability to restart TGS","perms.admin.changeversion":"Update TGS","perms.admin.changeversion.desc":"Abilty to update TGS to a newer version","perms.admin.editownpassword":"Change Own Password","perms.admin.editownpassword.desc":"Ability to change their own password","perms.admin.readusers":"View Users","perms.admin.readusers.desc":"Ability to view all users","perms.admin.downloadlogs":"Access TGS logs","perms.admin.downloadlogs.desc":"Ability to view and download all TGS logs","perms.admin.editownoauthconnections":"Edit own external identity providers","perms.admin.editownoauthconnections.desc":"Ability to edit their own identity providers(oauth)","perms.admin.uploadversion":"Upload Version .zip","perms.admin.uploadversion.desc":"Ability to update the server with an uploaded .zip update package.","perms.instance":"Instance Manager Permissions","perms.instance.read":"Read Accessible Instances","perms.instance.read.desc":"Ability to list and view instances the user is allowed access to. WARNING: Users who know the instance ID can still use the API to edit it using other permissions even if they lack this one.","perms.instance.create":"Create Instances","perms.instance.create.warning":"The current user is not permitted to create instances","perms.instance.create.desc":"Ability to create new instances","perms.instance.rename":"Rename Instances","perms.instance.rename.desc":"Ability to rename instances","perms.instance.relocate":"Relocate Instances","perms.instance.relocate.desc":"Ablity to change the location of an instance on the file system","perms.instance.setonline":"Change Instance Online Status","perms.instance.setonline.desc":"Ability to set an instance as online or offline","perms.instance.delete":"Delete Instance","perms.instance.delete.desc":"Ablity to delete an instance","perms.instance.list":"Read All Instances","perms.instance.list.desc":"Ability to list and view all instances","perms.instance.setconfiguration":"Set Instance Configuration Mode","perms.instance.setconfiguration.desc":"Ability to set an instance\'s static file editing mode","perms.instance.setautoupdate":"Set Instance Autoupdate Interval","perms.instance.setautoupdate.desc":"Ability to set an instance\'s interval for automatic code updates","perms.instance.setchatbotlimit":"Set Instance Chatbot Limit","perms.instance.setchatbotlimit.desc":"Ability to change an instance\'s maximum amounts of bots","perms.instance.grantpermissions":"Grant All Permissions","perms.instance.grantpermissions.desc":"Ability to grant themselves all permissions on any instance","perms.instancepermissionset":"Instance Admin","perms.instancepermissionset.cantedit":"You require the \\"Write\\" instance permission set flag to change these values","perms.instancepermissionset.create":"Create new Instance Permission Sets","perms.instancepermissionset.create.desc":"Allows you to grant access to this instance to other TGS users/groups.","perms.instancepermissionset.read":"Read Instance Permission Sets","perms.instancepermissionset.read.desc":"Allows you to read the permissions in this instance of other TGS users/groups already registered.","perms.instancepermissionset.write":"Write Instance Permission Sets","perms.instancepermissionset.write.desc":"Allows you to change or delete the permissions in this instance of both you and other TGS users/groups already registered.","perms.repository":"Repository","perms.repository.cancelpendingchanges":"Cancel Pending Changes","perms.repository.cancelpendingchanges.desc":"Allows cancelling active repository jobs (Except clone jobs).","perms.repository.setorigin":"Clone Repository","perms.repository.setorigin.desc":"Allows cloning the repository from a remote if it doesn\'t exist.","perms.repository.setsha":"Checkout SHA","perms.repository.setsha.desc":"Allows checking out a specific commit by it\'s SHA.","perms.repository.mergepullrequest":"Create Test Merge","perms.repository.mergepullrequest.desc":"Allows test merging a commit request from the remote.","perms.repository.updatebranch":"Update Branch","perms.repository.updatebranch.desc":"Allows hard reset and merge updates to the repository\'s current branch.","perms.repository.changecommitter":"Change Committer","perms.repository.changecommitter.desc":"Allows changing the username git commits are written as.","perms.repository.changetestmergecommits":"Change Remote Test Merge Settings","perms.repository.changetestmergecommits.desc":"Allows enabling/disabling the settings for pushing test merge commits to the remote in a temporary branch, posting comments when test merges occur, and creating GitHub deployments.","perms.repository.changecredentials":"Change Credentials","perms.repository.changecredentials.desc":"Allows changing the credentials used to access the repository.","perms.repository.setreference":"Checkout Branch/Tag","perms.repository.setreference.desc":"Allows checking out a specific branch/tag by it\'s name.","perms.repository.read":"Read Status Information","perms.repository.read.desc":"Allows reading repository status information (HEAD/Committer/Active Test Merges/etc...).","perms.repository.changeautoupdatesettings":"Change Auto Update Test Merge Settings","perms.repository.changeautoupdatesettings.desc":"Allows changing the setting to preserve test merges while updating a branch or synchronize with the remote.","perms.repository.delete":"Delete Repository","perms.repository.delete.desc":"Allows deleting the cloned repository from disk in order to clone another one.","perms.repository.cancelclone":"Cancel Clone","perms.repository.cancelclone.desc":"Allows cancelling active repository clone jobs.","perms.repository.changesubmoduleupdate":"Change Submodule Update","perms.repository.changesubmoduleupdate.desc":"Allows changing the setting to update submodules with branches.","perms.engine":"Engine","perms.engine.readactive":"Read Active Version","perms.engine.readactive.desc":"Allows reading the active engine version.","perms.engine.listinstalled":"List Installed Versions","perms.engine.listinstalled.desc":"Allows reading all installed engine versions.","perms.engine.installofficialorchangeactivebyondversion":"Activate/Install BYOND Version","perms.engine.installofficialorchangeactivebyondversion.desc":"Allows installation and activation of BYOND versions downloaded from the official website. Pre-installed custom versions may also be activated","perms.engine.installofficialorchangeactiveopendreamversion":"Activate/Install OpenDream Version","perms.engine.installofficialorchangeactiveopendreamversion.desc":"Allows installation and activation of OpenDream versions downloaded from the configured git. Pre-installed custom versions may also be activated","perms.engine.cancelinstall":"Cancel Install Job","perms.engine.cancelinstall.desc":"Allows cancelling install jobs.","perms.engine.installcustombyondversion":"Install Custom BYOND Version","perms.engine.installcustombyondversion.desc":"Allows installing a BYOND version uploaded from a zip file.","perms.engine.installcustomopendreamversion":"Install Custom OpenDream Version","perms.engine.installcustomopendreamversion.desc":"Allows installing an OpenDream version uploaded from a zip file.","perms.engine.deleteinstall":"Delete Engine Version","perms.dreammaker":"Deployment","perms.dreammaker.read":"Read Deployment Settings","perms.dreammaker.read.desc":"Allows reading information about the deployment settings.","perms.dreammaker.compile":"Create Deployment","perms.dreammaker.compile.desc":"Allows starting new deployment jobs.","perms.dreammaker.cancelcompile":"Cancel Deployment","perms.dreammaker.cancelcompile.desc":"Allows cancelling deployment jobs.","perms.dreammaker.setdme":"Set .dme Path","perms.dreammaker.setdme.desc":"Allows overriding TGS\' automatic .dme detection in favor of using a different .dme.","perms.dreammaker.setapivalidationport":"Set Validation Port","perms.dreammaker.setapivalidationport.desc":"Allows changing the port used by DreamDaemon to check for the presence of the DMAPI while deploying.","perms.dreammaker.compilejobs":"Read Compile Jobs","perms.dreammaker.compilejobs.desc":"Allows reading information about the current and past deployments.","perms.dreammaker.setsecuritylevel":"Set Validation Security Level","perms.dreammaker.setsecuritylevel.desc":"Allows changing the BYOND security level used to validate the DMAPI.","perms.dreammaker.setapivalidationrequirement":"Set Validation Requirement","perms.dreammaker.setapivalidationrequirement.desc":"Allows setting whether or not the DMAPI is required for successful deployments.","perms.dreammaker.settimeout":"Set Deployment Timeout","perms.dreammaker.settimeout.desc":"Allows setting the amount of time allowed to pass before a deployment job is automatically cancelled.","perms.dreammaker.setcompilerarguments":"Set Additional Compiler Arguments","perms.dreammaker.setcompilerarguments.desc":"Allows setting additional compiler arguments to be passed on the command line.","perms.dreamdaemon":"Server","perms.dreamdaemon.readrevision":"View Current Compile Jobs","perms.dreamdaemon.readrevision.desc":"Allows reading information about the latest and staged compile jobs.","perms.dreamdaemon.setlogoutput":"Set DreamDaemon Output Logging","perms.dreamdaemon.setlogoutput.desc":"Allows setting whether or not DreamDaemon will log it\'s output to the Diagnostics folder.","perms.dreamdaemon.setautostart":"Set Autostart","perms.dreamdaemon.setautostart.desc":"Allows setting whether or not DreamDaemon will start automatically when the instance comes online.","perms.dreamdaemon.setport":"Set Game Port","perms.dreamdaemon.setport.desc":"Allows setting the port DreamDaemon listens on.","perms.dreamdaemon.setsecurity":"Set Security Level","perms.dreamdaemon.setsecurity.desc":"Allows setting the BYOND security level to run the game at (Note: a higher DMAPI setting stored in a compile job can override this).","perms.dreamdaemon.readmetadata":"Read Settings","perms.dreamdaemon.readmetadata.desc":"Allows reading the current DreamDaemon settings.","perms.dreamdaemon.setwebclient":"Set Webclient","perms.dreamdaemon.setwebclient.desc":"Allows enabling/disabling use of the BYOND webclient.","perms.dreamdaemon.softrestart":"Graceful Restart","perms.dreamdaemon.softrestart.desc":"Allows requesting the server is gracefully restarted (Process restart on world.Reboot()).","perms.dreamdaemon.softshutdown":"Graceful Shutdown","perms.dreamdaemon.softshutdown.desc":"Allows requesting the server is gracefully shutdown (Process terminiation on world.Reboot()).","perms.dreamdaemon.restart":"Hard Restart","perms.dreamdaemon.restart.desc":"Allows immediately restarting the DreamDaemon process.","perms.dreamdaemon.shutdown":"Hard Shutdown","perms.dreamdaemon.shutdown.desc":"Allows immediately terminating the DreamDaemon process.","perms.dreamdaemon.start":"Server Launch","perms.dreamdaemon.start.desc":"Allows launching DreamDaemon from a shutdown state.","perms.dreamdaemon.setstartuptimeout":"Set Startup Timeout","perms.dreamdaemon.setstartuptimeout.desc":"Allows changing the idle/DMAPI detection timeout for starting new servers before it\'s considered a failure. This also applies to DreamDaemon instances used during deployments.","perms.dreamdaemon.sethealthcheckinterval":"Set Health Check Interval","perms.dreamdaemon.sethealthcheckinterval.desc":"Allows changing the interval at which health check Topics are sent.","perms.dreamdaemon.createdump":"Create Process Dump","perms.dreamdaemon.createdump.desc":"Allows creating dump files of DreamDaemon while it is running.","perms.dreamdaemon.settopictimeout":"Set Topic Timeout","perms.dreamdaemon.settopictimeout.desc":"Allows setting the timeout interval for sending Topics to the server before being considered a failure.","perms.dreamdaemon.setadditionalparameters":"Set Additional Parameters","perms.dreamdaemon.setadditionalparameters.desc":"Allows adding additional DreamDaemon launch parameters (Not command line arguments).","perms.dreamdaemon.setvisibility":"Set Visibility","perms.dreamdaemon.setvisibility.desc":"Allows changing the DreamDaemon visibility setting.","perms.dreamdaemon.setprofiler":"Set Profiler","perms.dreamdaemon.setprofiler.desc":"Allows setting the `-profile` command line option.","perms.dreamdaemon.setmapthreads":"Set Map Threads","perms.dreamdaemon.setmapthreads.desc":"Allows setting the `-map-threads` command line option.","perms.dreamdaemon.broadcastmessage":"Send Broadcast Messages","perms.dreamdaemon.broadcastmessage.desc":"Allows sending arbitrary messages from TGS clients to the running game server for broadcasting to players.","perms.dreamdaemon.setminidumps":"Change Core Dump Type","perms.dreamdaemon.setminidumps.desc":"Allows switching between creating minidumps and full core dumps.","perms.chatbots":"Chat Bots","perms.chatbots.writeenabled":"Set Enabled","perms.chatbots.writeenabled.desc":"Allows activation and deactivation of chat bots.","perms.chatbots.writeprovider":"Set Provider","perms.chatbots.writeprovider.desc":"Allows changing the chat provider of chat bots.","perms.chatbots.writechannels":"Set Channels","perms.chatbots.writechannels.desc":"Allows changing the channels chat bots are connected to.","perms.chatbots.writeconnectionstring":"Set Connection String","perms.chatbots.writeconnectionstring.desc":"Allows adjusting the TGS connection string of chat bots.","perms.chatbots.readconnectionstring":"Read Connection String","perms.chatbots.readconnectionstring.desc":"Allows reading the TGS connection string of chat bots. May contain sensitive data","perms.chatbots.read":"Read","perms.chatbots.read.desc":"Allows reading all chat bot information aside from their connection strings.","perms.chatbots.create":"Create","perms.chatbots.create.desc":"Allows the creation of bew chat bots.","perms.chatbots.delete":"Delete","perms.chatbots.delete.desc":"Allows the deletion of chat bots.","perms.chatbots.writename":"Set Name","perms.chatbots.writename.desc":"Allows changing the name of chat bots.","perms.chatbots.writereconnectioninterval":"Set Reconnection Interval","perms.chatbots.writereconnectioninterval.desc":"Allows changing the interval at which chat bots that have lost connection to their providers will attempt to reconnect.","perms.chatbots.writechannellimit":"Set Channel Limit","perms.chatbots.writechannellimit.desc":"Allows changing the channel limit of chat bots.","perms.configuration":"Files & Scripts","perms.configuration.read":"Read Files","perms.configuration.read.desc":"Allows downloading configuration files where permitted.","perms.configuration.write":"Modify Files","perms.configuration.write.desc":"Allows uploading, overwriting, and deleting configuration files where permitted.","perms.configuration.list":"Traverse Directories","perms.configuration.list.desc":"Allows listing the contents of configuration directories where permitted.","perms.configuration.delete":"Delete Directories","perms.configuration.delete.desc":"Allows deleting empty configuration directories where permitted.","perms.group":"Group","perms.group.create":"Create Group","perms.group.warning":"This user is linked to the \\"{group}\\" group. Any change to the permissions will be applied to the group.","perms.group.delete.tooltip":"This group contains more than 0 users. Remove all users before deleting the group.","perms.group.rename.tooltip":"Rename","perms.group.none":"No group","perms.group.current":"Current Group: ","perms.group.cantlist":"You lack the permission to list all users. You need it to be able to list all groups","config.githubtoken":"Github token","config.githubtoken.desc":"You can supply a private authorization token for github to bypass some rate limiting on the github API.","config.apipath":"TGS API Path","config.apipath.desc":"Sets the API client server\'s path.","config.jobpollinactive":"Inactive job poll delay (s)","config.jobpollinactive.desc":"After how many seconds should we check for new jobs if we dont have any active jobs","config.jobpollactive":"Active job poll delay (s)","config.jobpollactive.desc":"After how many seconds should we check for new jobs if we know about a job","config.jobswidgetdisplay":"Jobs Widget Display","config.jobswidgetdisplay.desc":"Display mode for the instance jobs widget","config.jobswidgetdisplay.enum.always":"Always display","config.jobswidgetdisplay.enum.auto":"Display when there are jobs","config.jobswidgetdisplay.enum.never":"Never display","config.instanceprobetimer":"Instance permission poll delay","config.instanceprobetimer.desc":"After how many seconds should we update the list of instances","config.itemsperpage":"Items Per Page","config.itemsperpage.desc":"This allows you to configure the amount of items shown at once in lists. Note that not all lists support this option yet.","config.instanceeditsidebar":"Instance Edit Sidebar","config.instanceeditsidebar.desc":"This controls how the left sidebar menu acts in the instance edit page.","config.instanceeditsidebar.enum.auto":"Expand on hover","config.instanceeditsidebar.enum.collapse":"Always collapsed","config.instanceeditsidebar.enum.expand":"Always expanded","config.showjson":"Show JSON objects","config.showjson.desc":"Most pages will display the underlying json data if enabled.","config.manualpr":"Show manual PR entry on repository page","config.manualpr.desc":"Controls whether or not an input box is displayed to manually testmerge a pr based on its PR number. This option is ignored and the manual PR entry box is always displayed when using a repository hosted on GitLab.","config.manualreset":"Show force reset switch on repository page","config.manualreset.desc":"Controls whether or not an input box is displayed to force a repository reset. This option is ignored and the manual PR entry box is always displayed when using a repository hosted on GitLab.","config.restjobs2":"Force HTTP Polling for Job Updates","config.restjobs2.desc":"Workaround for if SignalR job updates are failing","loading.loading":"Loading...","loading.login":"Logging in...","loading.page":"Loading page: ","loading.page.notfound":"Loading page: NotFound","loading.perms":"Updating permissions...","loading.app":"Loading app...","loading.admin":"Loading admin info...","loading.version":"Loading versions...","loading.updating":"Updating server...","loading.logs":"Loading Log(s)...","loading.info":"Loading Information...","loading.serverinfo":"Loading Server Information...","loading.passwd":"Changing password...","loading.userlist":"Loading user list...","loading.user.load":"Loading user information...","loading.user.save":"Saving user information...","loading.user.create":"Creating user...","loading.instance.list":"Loading instance list...","loading.instance.move":"Relocating instance...","loading.instance.files":"Performing file operation...","loading.instance.jobs.list":"Loading job list...","loading.instance":"Loading instance...","loading.instance.server":"Loading watchdog information...","loading.repo.cloning":"Cloning repository...","loading.repo.prs":"Loading PRs...","loading.repo.commits":"Loading Commits...","loading.repo.busy":"The repository is currently busy...","loading.deployments":"Loading DreamMaker Settings...","loading.compile_jobs":"Loading Compile Jobs...","loading.chat":"Loading Chat Bots...","loading.byond":"Loading BYOND Version Information...","loading.instance.perms":"Loading Instance Permissions...","fields.instance.name":"Instance Name","fields.instance.path":"Path on disk","fields.instance.chatbotlimit":"Max chatbots","fields.instance.filemode":"Static File Edit Mode","fields.instance.autoupdate":"Autoupdate Interval in minutes (0 to disable)","fields.instance.chat.channel.admin":"Admin Channel","fields.instance.chat.channel.admin.tip":"This channel can be used to receive messages and issue TGS chat commands designated as admin only","fields.instance.chat.channel.discord":"Channel ID","fields.instance.chat.channel.discord.tip":"Right click a channel with developer mode enabled to see the option to copy its ID","fields.instance.chat.channel.irc":"IRC Channel","fields.instance.chat.channel.irc.tip":"Include the \'#\'","fields.instance.chat.channel.tag":"DMAPI Tag","fields.instance.chat.channel.tag.tip":"A string associated with this channel in the DMAPI","fields.instance.chat.channel.updates":"Deployments Channel","fields.instance.chat.channel.updates.tip":"This channel will receive TGS deployment messages","fields.instance.chat.channel.system":"System Channel","fields.instance.chat.channel.system.tip":"This channel will receive TGS system messages (Restarts and updates)","fields.instance.chat.channel.watchdog":"Watchdog Channel","fields.instance.chat.channel.watchdog.tip":"This channel will receive live updates as to the state of the active DreamDaemon process","fields.instance.chat.create.channel":"Add Channel","fields.instance.chat.create.discord.token":"Bot Token","fields.instance.chat.create.discord.token.tip":"Bot access token retrieved from Discord developers portal","fields.instance.chat.create.discord.output":"Show Deployment Messages","fields.instance.chat.create.discord.output.tip":"When messages from deployment jobs will be shown in Watchdog-type channels","fields.instance.chat.create.discord.based.tip":"Know your meme","fields.instance.chat.create.discord.branding":"Deployment Embed Branding","fields.instance.chat.create.discord.branding.tip":"If the tgstation-server logo, name, and link to repo are shown at the top of deployment embeds","fields.instance.chat.create.irc.address":"Server Address","fields.instance.chat.create.irc.address.tip":"IP/Domain only","fields.instance.chat.create.irc.port":"Server Port","fields.instance.chat.create.irc.nick":"Bot Nickname","fields.instance.chat.create.irc.pass":"Server Password","fields.instance.chat.create.irc.pass.tip":"Setting a password on the server for your IRC bot is HIGHLY recommended","fields.instance.chat.create.irc.passtype":"Server Password Type","fields.instance.chat.create.irc.ssl":"Connect with SSL","fields.instance.chat.create.irc.ssl.tip":"Note: This is dependent on the IRC server\'s configuration","fields.instance.chat.create.save":"Create Chat Bot","fields.instance.chat.edit.connection":"Connection String","fields.instance.chat.edit.connection.tip":"Contains sensitive information. Consider remaking the bot instead of modifying this due to the internal formatting","fields.instance.chat.edit.connection.unloaded":"Not Loaded. Edit available","fields.instance.chat.edit.connection.deny":"Lacking read permission. Edit may still available","fields.instance.chat.enabled":"Enabled","fields.instance.chat.enabled.tip":"If the bot is to be online with the instance","fields.instance.chat.limit":"Channel Limit","fields.instance.chat.limit.tip":"Maximum number of channels this bot can interact with","fields.instance.chat.provider":"Chat Service Provider","fields.instance.chat.reconnect":"Reconnection Interval (Minutes)","fields.instance.chat.reconnect.tip":"The period at which the bot will attempt to reconnect to the chat service if unexpectedly disconnected","fields.instance.chat.name":"Chat Bot Name","fields.instance.chat.name.tip":"Internal name of the chat bot","fields.instance.filemode.Disallowed":"No File Management.","fields.instance.filemode.HostWrite":"Authorized users can edit any file.","fields.instance.filemode.SystemIdentityWrite":"Users using a system identity can edit files their user has access to.","fields.instance.files.create":"Create Directory or Upload File","fields.instance.files.create.directory":"Create directory instead of file","fields.instance.files.create.name":"File system entry name","fields.instance.files.create.name.tip":"The name of the file or directory being created (extension included for files)","fields.instance.perms.owner":"Editing Instance Permissions For","fields.instance.perms.owner.switch":"Switch","fields.instance.watchdog.autostart":"Start server with instance","fields.instance.watchdog.autostartprofiler":"Start BYOND profiler automatically","fields.instance.watchdog.allowwebclient":"Allow BYOND web client connections","fields.instance.watchdog.logoutput":"Log DreamDaemon Output","fields.instance.watchdog.minidumps":"Use minidumps instead of full core dumps","fields.instance.watchdog.visibility":"BYOND hub visibility","fields.instance.watchdog.visibility.Public":"Public","fields.instance.watchdog.visibility.Private":"Private","fields.instance.watchdog.visibility.Invisible":"Invisible","fields.instance.watchdog.securitylevel":"BYOND security level","fields.instance.watchdog.securitylevel.Trusted":"Trusted","fields.instance.watchdog.securitylevel.Safe":"Safe","fields.instance.watchdog.securitylevel.Ultrasafe":"Ultra-Safe","fields.instance.watchdog.port":"Network port","fields.instance.watchdog.timeout.startup":"Startup timeout (seconds)","fields.instance.watchdog.timeout.topic":"Topic timeout (milliseconds)","fields.instance.watchdog.healthcheck":"Health Check Timeout (seconds)","fields.instance.watchdog.dumpOnHealthCheckRestart":"Create process dump on health check fail restart","fields.instance.watchdog.additionalparams":"Additional command line parameters","fields.instance.watchdog.mapthreads":"Map Threads Count (0 for default)","fields.instance.watchdog.broadcast":"Broadcast Message","fields.instance.watchdog.broadcast.desc":"A message to broadcast to players using the DMAPI. Requires the server be running with an interop version >=5.7.0","fields.instance.repository.origincheckoutsha":"Origin SHA","fields.instance.repository.origincheckoutsha.desc":"SHA of the origin commit","fields.instance.repository.checkoutsha":"Checkout SHA","fields.instance.repository.checkoutsha.desc":"SHA of the commit to checkout","fields.instance.repository.reference":"Reference","fields.instance.repository.reference.desc":"Set this to the branch, commit or tag you wish to track","fields.instance.repository.committerName":"Committer Name","fields.instance.repository.committerEmail":"Committer Email","fields.instance.repository.accessUser":"Access Username","fields.instance.repository.accessUser.desc":"These credentials will be used when cloning the repository or performing authenticated actions","fields.instance.repository.accessToken":"New Access Password","fields.instance.repository.accessToken.desc":"For github, this will be a PAT(Private Authentication Token), for other providers, this will be a password","fields.instance.repository.clearAccessToken":"Clear Access Credentials","fields.instance.repository.pushTestMergeCommits":"Push Test Merge Commits","fields.instance.repository.pushTestMergeCommits.desc":"This will push commits created by test merges to a temporary branch on the remote. Requires access credentials.","fields.instance.repository.createGitHubDeployments":"Create GitHub Deployments","fields.instance.repository.createGitHubDeployments.desc":"Requires access credentials","fields.instance.repository.showTestMergeCommitters":"Show test merge commiters in public metadata","fields.instance.repository.showTestMergeCommitters.desc":"Shows who test merged a PR. This only applies to future commits.","fields.instance.repository.autoUpdatesKeepTestMerges":"Preserve test merges when auto updating","fields.instance.repository.autoUpdatesKeepTestMerges.desc":"If enabled, auto updates may fail if a merge conflict occurs.","fields.instance.repository.autoUpdatesSynchronize":"Push new commits to origin during auto-update","fields.instance.repository.autoUpdatesSynchronize.desc":"Used for example, with changelog scripts depending on the setup","fields.instance.repository.postTestMergeComment":"Post comment when test merge is deployed","fields.instance.repository.postTestMergeComment.desc":"This will post a github comment each time a test merge is deployed or updated","fields.instance.repository.updateSubmodules":"Update submodules automatically","fields.instance.repository.updateSubmodules.desc":"Submodules will be updated automatically when resetting, checking out or adding a test merge. This is not recursive","fields.instance.repository.url":"Remote URL","fields.instance.repository.ref":"Remote reference (branch)","fields.instance.repository.gituser":"Git access username","fields.instance.repository.gitpassword":"Git access password","fields.instance.repository.enablesubmodules":"Enable submodules","fields.instance.deploy.projectname":"DME name (blank for auto)","fields.instance.deploy.projectname.desc":"This can also be a relative path and shouldn\'t include the file extension","fields.instance.deploy.compilerargs":"Additional Compiler Arguments","fields.instance.deploy.compilerargs.desc":"These are added to compiler command lines right before the path to the .dme","fields.instance.deploy.timeout":"Job timeout (in minutes)","fields.instance.deploy.timeout.desc":"Time before a compile job is abandonned and cancelled","fields.instance.deploy.apiport":"DMAPI port (0 for auto)","fields.instance.deploy.apiport.desc":"This port should not be public","fields.instance.deploy.seclevel":"DMAPI validation security level","fields.instance.deploy.seclevel.Trusted":"Trusted","fields.instance.deploy.seclevel.Safe":"Safe","fields.instance.deploy.seclevel.Ultrasafe":"Ultra-Safe","fields.instance.deploy.seclevel.desc":"This is only used for the DMAPI validation","fields.instance.deploy.validateapi":"Validate DMAPI","fields.instance.deploy.validateapi.desc":"This will check that the DMAPI initializes successfully without any runtimes","warning.screensize.header":"Screen size warning","warning.screensize":"The TGS webpanel does not guarentee support for viewports with a width of under 992px."}')}}]); \ No newline at end of file diff --git a/webpanel/5.7.0/main.1049fd3651ff85aa05ae.bundle.js b/webpanel/5.7.0/main.1049fd3651ff85aa05ae.bundle.js new file mode 100644 index 00000000..78b245e2 --- /dev/null +++ b/webpanel/5.7.0/main.1049fd3651ff85aa05ae.bundle.js @@ -0,0 +1,2 @@ +!function(){var e,t,n,r,o,s={97888:function(e,t,n){"use strict";var r=n(67294),o=n(35005),s=n(44012),a=n(5977),i=n(9635);class l extends r.Component{render(){return r.createElement(i.Z,{title:"generic.accessdenied"},r.createElement(o.Z,{variant:"danger",className:"float-right",onClick:()=>{this.props.history.goBack()}},r.createElement(s.Z,{id:"generic.goback"})))}}t.Z=(0,a.EN)(l)},3e3:function(e,t,n){"use strict";n.d(t,{hP:function(){return h},iT:function(){return m}});var r=n(28359),o=n(67294),s=n(88375),a=n(35005),i=n(37959),l=n(44012),c=n(96846),d=n(86755);class u extends o.Component{constructor(e){super(e),this.state={popup:!1}}render(){if(!this.props.error)return"";const e=()=>this.setState({popup:!1});return o.createElement(s.Z,{className:"clearfix",variant:"error",dismissible:!!this.props.onClose,onClose:this.props.onClose},o.createElement(l.Z,{id:this.props.error.code||"error.app.undefined"}),o.createElement("hr",null),o.createElement(a.Z,{variant:"danger",className:"float-right",onClick:()=>this.setState({popup:!0})},o.createElement(l.Z,{id:"generic.details"})),o.createElement(i.Z,{centered:!0,show:this.state.popup,onHide:e,size:"lg"},o.createElement(i.Z.Header,{closeButton:!0},o.createElement(i.Z.Title,null,o.createElement(l.Z,{id:this.props.error.code||"error.app.undefined"}))),o.createElement(i.Z.Body,{className:"text-danger pb-0"},this.props.error.desc?.type===c._T.LOCALE?o.createElement(l.Z,{id:this.props.error.desc.desc||"error.api.empty"}):this.props.error.desc?.desc?this.props.error.desc.desc:"",o.createElement("hr",null),o.createElement(r.Z,null,o.createElement("code",{className:"bg-darker d-block pre-wrap p-2 pre-scrollable"},`Webpanel Version: ${d.q4}\nWebpanel Mode: ${d.IK}\nAPI Version: ${d.Gn}\n\nError Code: ${this.props.error.code}\nError Description: ${this.props.error.desc?this.props.error.desc.desc:"No description"}\n\nAdditional Information:\n${this.props.error.extendedInfo}`.replace(/\\/g,"\\\\")))),o.createElement(i.Z.Footer,null,o.createElement("span",{className:"font-italic mr-auto"},o.createElement(l.Z,{id:"generic.debugwarn"})),o.createElement(a.Z,{variant:"secondary",onClick:e},o.createElement(l.Z,{id:"generic.close"})))))}}function m([,e],t){e((e=>{const n=Array.from(e);return n.push(t),n}))}function h([e,t]){return e.map(((e,n)=>{if(e)return o.createElement(u,{key:n,error:e,onClose:()=>t((e=>{const t=Array.from(e);return t[n]=void 0,t}))})}))}t.ZP=u},9635:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(67294),o=n(88375),s=n(44012);function a(e){return r.createElement(o.Z,{className:"clearfix",variant:"error"},r.createElement(s.Z,{id:e.title}),e.body?r.createElement(r.Fragment,null,r.createElement("hr",null),r.createElement(s.Z,{id:e.body})):e.children?r.createElement(r.Fragment,null,r.createElement("hr",null),e.children):null)}},93128:function(e,t,n){"use strict";n.d(t,{Z:function(){return E}});var r=n(67814),o=n(67294),s=n(35005),a=n(15293),i=n(51479),l=n(10729),c=n(61318),d=n(17863),u=n(43489),m=n(44012),h=n(48272),p=n(28359),f=n(37959),g=n(48509);function v(e){const[t,n]=(0,o.useState)(!1);return o.createElement(o.Fragment,null,o.createElement(s.Z,{variant:"danger",className:"d-inline-block",onClick:()=>n(!0),size:"sm"},o.createElement(m.Z,{id:"generic.errordetails",values:{info:void 0!==e.job.errorCode&&null!==e.job.errorCode?g.jK[e.job.errorCode]:"NoCode"}})),o.createElement(f.Z,{centered:!0,show:t,onHide:()=>n(!1),size:"lg"},o.createElement(f.Z.Header,{closeButton:!0},o.createElement(f.Z.Title,null,o.createElement(m.Z,{id:e.job.description}))),o.createElement(f.Z.Body,{className:"text-danger pb-0"},o.createElement(m.Z,{id:"view.instance.jobs.error"}),":"," ",void 0!==e.job.errorCode&&null!==e.job.errorCode?g.jK[e.job.errorCode]:"NoCode",o.createElement("hr",null),o.createElement(p.Z,null,o.createElement("code",{className:"bg-darker d-block pre-wrap p-2 pre-scrollable"},e.job.exceptionDetails))),o.createElement(f.Z.Footer,null,o.createElement(s.Z,{variant:"secondary",onClick:()=>n(!1)},o.createElement(m.Z,{id:"generic.close"})))))}function b(){return b=Object.assign||function(e){for(var t=1;t{this.props.onClose&&this.props.onClose(e)}},o.createElement(d.Z,{closeButton:!!e.stoppedAt&&!!this.props.onClose,className:`bg-${f}`},"#",e.id,": ",e.description),o.createElement(c.Z,{className:"pt-1 text-white"},e.stage?o.createElement("div",{className:"mb-2"},"\u25b6",e.stage):null,o.createElement(m.Z,{id:"app.job.started"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-started`},t.toLocaleString())},(({ref:e,...t})=>o.createElement("span",b({},t,{ref:e}),o.createElement(h.Z,{value:n,numeric:"auto",updateIntervalInSeconds:1})))),o.createElement("br",null),o.createElement(m.Z,{id:"app.job.startedby"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-startedby`},o.createElement(m.Z,{id:"generic.userid"}),e.startedBy.id)},(({ref:t,...n})=>o.createElement("span",b({ref:t},n),e.startedBy.name))),o.createElement("br",null),o.createElement("br",null),e.stoppedAt?o.createElement(o.Fragment,null,o.createElement(m.Z,{id:e.cancelled?"app.job.cancelled":"app.job.completed"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-stopped`},t.toLocaleString())},(({ref:e,...t})=>o.createElement("span",b({},t,{ref:e}),o.createElement(h.Z,{value:p,numeric:"auto",updateIntervalInSeconds:1})))),o.createElement("br",null)):"",e.cancelledBy?o.createElement(o.Fragment,null,o.createElement(m.Z,{id:"app.job.cancelledby"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-createdby`},o.createElement(m.Z,{id:"generic.userid"}),e.startedBy.id)},(({ref:t,...n})=>o.createElement("span",b({ref:t},n),e.cancelledBy.name))),o.createElement("br",null)):"",void 0!==e.errorCode||void 0!==e.exceptionDetails?o.createElement(v,{job:e}):"",o.createElement("div",{className:"d-flex mt-2",style:{height:"1.5rem"}},o.createElement(i.Z,{className:"text-darker font-weight-bold flex-grow-1 h-unset",animated:!e.stoppedAt,label:"number"==typeof e.progress?`${e.progress.toString()}%`:void 0,now:"number"==typeof e.progress?e.progress:100,striped:!0,variant:f}),e.canCancel&&!e.stoppedAt?o.createElement(s.Z,{style:{padding:"0 1em"},className:"ml-1",variant:"danger",onClick:()=>this.props.onCancel(e)},o.createElement(r.G,{icon:"times",className:"d-block"})):null)))}}},20271:function(e,t,n){"use strict";n.d(t,{Z:function(){return b}});var r=n(51436),o=n(67814),s=n(67294),a=n(15881),i=n(35005),l=n(15293),c=n(43489),d=n(44012),u=n(61090),m=n(27961),h=n(39521),p=n(3e3),f=n(93128),g=n(35855);function v(){return v=Object.assign||function(e){for(var t=1;t{const n=Array.from(t.ownerrors);return n.push(e),this.widgetRef.current&&(this.widgetRef.current.scrollTop=0),{ownerrors:n}}))}componentDidMount(){h.Z.on("jobsLoaded",this.handleUpdate),this.handleUpdate()}componentWillUnmount(){h.Z.removeListener("jobsLoaded",this.handleUpdate)}handleUpdate(){let e;this.currentTimeout&&(clearTimeout(this.currentTimeout),this.currentTimeout=null),h.Z.nextRetry?(e=h.Z.nextRetry.getSeconds()>(new Date).getSeconds()?h.Z.nextRetry.getSeconds()-(new Date).getSeconds():0,this.currentTimeout=setTimeout((()=>this.handleUpdate()),1e3)):e=null,this.setState({jobs:h.Z.jobsByInstance,errors:h.Z.errors,nextRetrySeconds:e,loading:!1,instances:h.Z.accessibleInstances})}async onCancel(e){await h.Z.cancelJob(e.id,(e=>this.addError(e)))&&(h.Z.fastmode=5)}onClose(e){h.Z.clearJob(e.id)}render(){if(!this.props.widget)return this.nested();let e,t=0;for(const e of this.state.jobs.values())t+=e.size;return e=m.ZP.jobswidgetdisplay.value!==m.zQ.NEVER&&(m.ZP.jobswidgetdisplay.value===m.zQ.ALWAYS||(t>0||this.state.errors.length>0)),s.createElement("div",{style:{position:"fixed",top:0,bottom:0,right:0,left:0,pointerEvents:"none",zIndex:5}},s.createElement(u.s,{className:"jobswidget "+(e?"":"invisible"),style:{pointerEvents:"auto",bottom:0,right:0},default:{width:"30vw",height:"50vh",x:document.documentElement.clientWidth-Math.min(.3*document.documentElement.clientWidth,350)-20,y:document.documentElement.clientHeight-.5*document.documentElement.clientHeight-20},maxWidth:350,minHeight:50,minWidth:110,bounds:"parent"},s.createElement("div",{className:"fancyscroll overflow-auto h-100",ref:this.widgetRef},s.createElement("h5",{className:"text-center text-darker font-weight-bold"},s.createElement(d.Z,{id:"view.instance.jobs.title"})),this.nested())))}nested(){return s.createElement("div",{className:this.props.widget?"d-sm-block":""},this.state.loading?s.createElement(g.Z,{text:"loading.instance.jobs.list"}):"",this.state.ownerrors.map(((e,t)=>{if(e)return s.createElement(p.ZP,{key:t,error:e,onClose:()=>this.setState((e=>{const n=Array.from(e.ownerrors);return n[t]=void 0,{ownerrors:n}}))})})),this.state.errors.length>0?s.createElement(s.Fragment,null,this.state.errors.map(((e,t)=>s.createElement("div",{key:t,style:{maxWidth:this.props.widget?350:"unset"}},s.createElement(p.ZP,{error:e})))),s.createElement(a.Z,null,0===this.state.nextRetrySeconds?s.createElement(d.Z,{id:"view.instance.jobs.reconnect_now"}):null!=this.state.nextRetrySeconds?s.createElement(d.Z,{id:"view.instance.jobs.reconnect_in",values:{seconds:this.state.nextRetrySeconds}}):s.createElement(d.Z,{id:"view.instance.jobs.reconnected_auth"}))):null,Array.from(this.state.jobs).sort(((e,t)=>e[0]-t[0])).map((([e,t])=>{let n=!1;t.forEach((e=>{e.stoppedAt&&(n=!0)}));const a=n?{marginTop:"5px",marginLeft:"20px"}:void 0;return s.createElement(s.Fragment,{key:e},s.createElement("div",{className:"bg-dark p-2 row"},s.createElement("div",{className:`col-${n?9:12} text-center`},s.createElement("div",{style:a},s.createElement(l.Z,{overlay:(e=>t=>s.createElement(c.Z,v({id:`tooltip-instance-${e}`},t),e))(e)},s.createElement(s.Fragment,null,this.state.instances.get(e)?.name??"Unknown"," ","(",s.createElement(d.Z,{id:"view.instance.jobs.jobtotal",values:{amount:t.size}}),")")))),n?s.createElement("div",{className:"col-3 text-right"},s.createElement(l.Z,{placement:"top",overlay:e=>s.createElement(c.Z,v({id:"clear-instance-jobs"},e),s.createElement(d.Z,{id:"view.instance.jobs.clearfinished"}))},s.createElement(i.Z,{variant:"outline-secondary",onClick:()=>t.forEach((e=>{e.stoppedAt&&h.Z.clearJob(e.id)})),className:"nowrap"},s.createElement(o.G,{icon:r.NBC})))):s.createElement(s.Fragment,null)),Array.from(t,(([,e])=>e)).sort(((e,t)=>t.id-e.id)).map((e=>s.createElement(f.Z,{job:e,width:this.props.width,key:e.id,onClose:this.onClose,onCancel:this.onCancel}))))})))}}b.defaultProps={widget:!0}},35855:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var r=n(67294),o=n(36968),s=n(44012),a=n(29697),i=n(94537);function l(){return l=Object.assign||function(e){for(var t=1;t{e.addEventListener("transitionend",t,!1)}},r.createElement("div",{className:n?"text-center":""},r.createElement(o.Z,l({variant:e||"secondary",className:n?`d-block mx-auto ${c??""}`:c,style:f,animation:t||"border"},p)),m?r.createElement(s.Z,{id:m}):"",h)))}}c.defaultProps={animation:"border",width:"50",widthUnit:"vmin",center:!0}},9310:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return x}});var r=n(51417),o=n(16566),s=n(38658),a=n(67814),i=n(67294),l=n(35005),c=n(31555),d=n(15881),u=n(32258),m=n(44012),h=n(5977),p=n(48509),f=n(11895),g=n(96846),v=n(53803),b=n(75631),E=n(42522),y=n(44615),w=n(86755),Z=n(1320),I=n(3e3),C=n(35855);class A extends i.Component{constructor(e){super(e),this.submit=this.submit.bind(this),console.log(Z.Mq.oautherrors),this.state={busy:!1,validated:!1,username:"",password:"",errors:Array.from(Z.Mq.oautherrors)}}async componentDidMount(){(window.sessionStorage.getItem("oauth")??E.Z.credentials?.type===f.P.OAuth)||"GITHUB"!==w.IK||await this.tryLoginDefault()}async tryLoginDefault(){if(this.props.loggedOut)return;(await b.Z.login(E.Z.default)).code===v.G.OK&&this.setState({redirectSetup:!0})}addError(e){this.setState((t=>{const n=Array.from(t.errors);return n.push(e),{errors:n}}))}render(){if(this.state.busy||E.Z.hasToken())return i.createElement(C.Z,{text:"loading.login"});if(!this.context.serverInfo)return i.createElement(C.Z,{text:"loading.serverinfo"});const e={[p.O4.GitHub]:i.createElement(a.G,{icon:s.zh,style:{width:"1.2em"}}),[p.O4.Discord]:i.createElement(a.G,{icon:o.om,style:{width:"1.2em"}}),[p.O4.TGForums]:i.createElement("img",{src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/c97e39e417e48a3282f9.svg",alt:"tglogo",style:{width:"1.2em"}}),[p.O4.Keycloak]:i.createElement("img",{src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/995a88a72fd6520c8505.png",alt:"keycloaklogo",style:{width:"1.2em"}}),[p.O4.InvisionCommunity]:i.createElement(a.G,{icon:r.n5f,style:{width:"1.2em"}})},t={GitHub:"#161b22",Discord:"#7289da",TGForums:void 0,Keycloak:void 0,InvisionCommunity:void 0};return i.createElement(c.Z,{className:"mx-auto",lg:5,md:8},this.state.errors.map(((e,t)=>{if(e)return i.createElement(I.ZP,{key:t,error:e,onClose:()=>this.setState((e=>{const n=Array.from(e.errors);return n[t]=void 0,{errors:n}}))})})),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.header"})),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.type.generic"})),i.createElement(u.Z,{validated:this.state.validated,onSubmit:this.submit},i.createElement(u.Z.Group,{controlId:"username"},i.createElement(u.Z.Label,null,i.createElement(m.Z,{id:"login.username"})),i.createElement(u.Z.Control,{type:"text",placeholder:"Enter username",onChange:e=>this.setState({username:e.target.value}),value:this.state.username,required:!0})),i.createElement(u.Z.Group,{controlId:"password"},i.createElement(u.Z.Label,null,i.createElement(m.Z,{id:"login.password"})),i.createElement(u.Z.Control,{type:"password",placeholder:"Password",onChange:e=>this.setState({password:e.target.value}),value:this.state.password,required:!0})),i.createElement(l.Z,{type:"submit",block:!0},i.createElement(m.Z,{id:"login.submit"})))),(this.context.serverInfo?.oAuthProviderInfos?.Discord||this.context.serverInfo?.oAuthProviderInfos?.GitHub||this.context.serverInfo?.oAuthProviderInfos?.Keycloak||this.context.serverInfo?.oAuthProviderInfos?.TGForums||this.context.serverInfo?.oAuthProviderInfos?.InvisionCommunity)&&i.createElement(i.Fragment,null,i.createElement("hr",null),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.type.oauth"})),Object.keys(this.context.serverInfo.oAuthProviderInfos??{}).map((n=>{const r=t[n];return i.createElement(l.Z,{key:n,block:!0,style:r?{background:r}:void 0,onClick:()=>this.startOAuth(n)},e[n],i.createElement("span",{className:"ml-1"},i.createElement(m.Z,{id:"login.oauth",values:{provider:n}})))}))))))}async startOAuth(e){if(!this.context.serverInfo)return void this.addError(new g.ZP(g.jK.APP_FAIL,{jsError:Error("serverInfo is null in startOAuth")}));const t=new Uint8Array(10);window.crypto.getRandomValues(t);const n=Array.from(t,(e=>e.toString(16).padStart(2,"0"))).join("");let r;const o=encodeURIComponent;switch(e){case p.O4.Discord:r=`https://discord.com/api/oauth2/authorize?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.Discord.clientId)}&scope=identify&state=${o(n)}`;this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri&&(r=`${r}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri)}`);break;case p.O4.GitHub:r=`https://github.com/login/oauth/authorize?client_id=${o(this.context.serverInfo.oAuthProviderInfos.GitHub.clientId)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.GitHub.redirectUri)}&state=${o(n)}&allow_signup=false`;break;case p.O4.Keycloak:r=`${this.context.serverInfo.oAuthProviderInfos.Keycloak.serverUrl}/protocol/openid-connect/auth?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.Keycloak.clientId)}&scope=openid&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.Keycloak.redirectUri)}`;break;case p.O4.TGForums:r=`https://tgstation13.org/phpBB/app.php/tgapi/oauth/auth?scope=user&client_id=${o(this.context.serverInfo.oAuthProviderInfos.TGForums.clientId)}&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.TGForums.redirectUri)}`;break;case p.O4.InvisionCommunity:r=`${this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.serverUrl}/oauth/authorize/?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.clientId)}&scope=profile&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.redirectUri)}`}const s=JSON.parse(window.sessionStorage.getItem("oauth")??"{}");return s[n]={provider:e,url:this.props.location.pathname},window.sessionStorage.setItem("oauth",JSON.stringify(s)),window.location.href=r,new Promise((e=>e()))}async submit(e){e.preventDefault(),this.setState({busy:!0});const t=await b.Z.login({type:f.P.Password,userName:this.state.username,password:this.state.password});t.code==v.G.ERROR&&(this.setState({busy:!1}),this.addError(t.error))}}A.contextType=y.f;var x=(0,h.EN)(A)},44615:function(e,t,n){"use strict";n.d(t,{f:function(){return r}});const r=n(67294).createContext(void 0)},86755:function(e,t,n){"use strict";n.d(t,{Gn:function(){return r},IK:function(){return s},UG:function(){return a},cV:function(){return i},q4:function(){return o}});const r="10.3.0",o="f09881e124bc2b4868d709657cfa440f2fb3de19",s="GITHUB",a="/app/",i="/"},16143:function(e,t,n){"use strict";window.publicPath,console.log("Public path:",n.p);n(51340),n(75029),n(5547),n(74462),n(17689);var r=n(73935),o=n(57806),s=n(39521),a=n(93379),i=n.n(a),l=n(39087),c={insert:"head",singleton:!1},d=(i()(l.Z,c),l.Z.locals,n(67294)),u=n(88375),m=n(10682),h=n(44012),p=n(29558),f=n(73727),g="5.7.0",v=n(96846),b=n(53803),E=n(75631),y=n(16942),w=n(42522),Z=n(50452),I=n(58007),C={insert:"head",singleton:!1},A=(i()(I.Z,C),I.Z.locals,n(51436)),x=n(67814),k=n(24214),S=n(15293),j=n(43489),L=n(35005),N=n(75040),O=n(13361),P=n(95602),$=n(5977),U=n(81249),G=n(22480),R=n(48509),T=n(44615),_=n(16964),D=n(70601),B=n(1320);function F(){return F=Object.assign||function(e){for(var t=1;t{this.props.history.push(B.$w.home.link??B.$w.home.route,{reload:!0})},className:"mr-auto"},this.renderVersion()),d.createElement(P.Z.Toggle,{className:"mr-2","aria-controls":"responsive-navbar-nav"}),d.createElement(P.Z.Collapse,{className:"text-right mr-2",style:{minWidth:"0px"}},d.createElement(O.Z,null,this.props.loggedIn?Object.values(this.state.categories).map((e=>{if(e.leader.cachedAuth)return 1==e.routes.length?d.createElement(O.Z.Item,{key:e.name},d.createElement(O.Z.Link,{onClick:()=>{this.props.history.push(e.leader.link??e.leader.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.leader.route,!e.leader.navbarLoose)},d.createElement(h.Z,{id:e.leader.name}))):d.createElement(O.Z.Item,{key:e.name},d.createElement(k.Z,{id:e.name+"-dropdown",title:d.createElement(h.Z,{id:e.leader.name})},Object.values(e.routes).filter((e=>e.cachedAuth)).length>=2?d.createElement(d.Fragment,null,d.createElement(k.Z.Item,{onClick:()=>{this.props.history.push(e.leader.link??e.leader.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.leader.route,!0)},d.createElement(h.Z,{id:e.leader.name})),e.routes.map((e=>{if(!e.catleader&&e.cachedAuth&&e.visibleNavbar)return d.createElement(k.Z.Item,{key:e.name,onClick:()=>{this.props.history.push(e.link??e.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.route,!e.navbarLoose)},d.createElement(h.Z,{id:e.name}))}))):""))})):d.createElement(O.Z.Item,null,d.createElement(O.Z.Link,{onClick:()=>{this.props.history.push(B.$w.home.link??B.$w.home.route,{reload:!0})},active:!0},d.createElement(h.Z,{id:"routes.login"})))),this.state.updateAvailable?d.createElement(S.Z,{placement:"right",overlay:e=>d.createElement(j.Z,F({id:"tgs-updated-tooltip"},e),d.createElement(h.Z,{id:"navbar.update"}))},d.createElement("h3",null,d.createElement(x.G,{className:"tgs-update-notification",onClick:()=>this.props.history.push(B.$w.admin_update.link??B.$w.admin_update.route,{reload:!0}),icon:A.RLE}))):d.createElement(d.Fragment,null),this.renderUser())))}renderVersion(){return this.context.serverInfo?.version?d.createElement(d.Fragment,null,d.createElement(h.Z,{id:"generic.appname"})," v",this.context.serverInfo.version):d.createElement(h.Z,{id:"loading.loading"})}renderUser(){return this.props.loggedIn?d.createElement(O.Z.Item,{className:"ml-auto"},d.createElement(N.Z,null,d.createElement(N.Z.Toggle,{id:"user-dropdown",type:"button",variant:"primary","data-toggle":"dropdown","aria-haspopup":"true","aria-expanded":"false"},this.context.user?this.context.user.name:d.createElement(h.Z,{id:"loading.loading"})),d.createElement(N.Z.Menu,{alignRight:!0,className:"text-right"},d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.info.link??B.$w.info.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.info"})),d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.config.link??B.$w.config.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.config"})),B.$w.passwd.cachedAuth?d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.passwd.link??B.$w.passwd.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.passwd"})):"",d.createElement(N.Z.Item,{onClick:()=>{E.Z.emit("purgeCache"),this.props.history.replace(this.props.location.pathname,{reload:!0})}},d.createElement(h.Z,{id:"navbar.purgecache"})),d.createElement(N.Z.Item,{onClick:()=>{this.props.history.replace(this.props.location.pathname,{reload:!0})}},d.createElement(h.Z,{id:"navbar.refresh"})),d.createElement(N.Z.Item,{onClick:this.logoutClick},d.createElement(h.Z,{id:"navbar.logout"}))))):d.createElement(d.Fragment,null,d.createElement(L.Z,{onClick:()=>{this.props.history.push(B.$w.config.link??B.$w.config.route,{reload:!0})},variant:"primary"},d.createElement(x.G,{icon:"cogs"})),d.createElement(L.Z,{onClick:()=>{this.props.history.push(B.$w.info.link??B.$w.info.route,{reload:!0})},variant:"primary"},d.createElement(x.G,{icon:"info-circle"})))}logoutClick(){E.Z.logout()}}M.contextType=T.f;var z=(0,$.EN)(M);function K(){return K=Object.assign||function(e){for(var t=1;t{t&&(e=Math.round(100*Math.random())%26)},overlay:t=>d.createElement(j.Z,K({id:"report-issue-tooltip"},t),d.createElement(h.Z,{id:`view.meme_${e}`}))},d.createElement("img",{className:"nowrap corner-logo",width:50,height:50,src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/b5616c99bf2052a6bbd7.svg"}))}}function W(){return W=Object.assign||function(e){for(var t=1;td.createElement(j.Z,W({id:"report-issue-tooltip"},e),d.createElement(h.Z,{id:"view.report"}))},d.createElement(L.Z,{className:"nowrap report-issue",onClick:()=>window.open("https://github.com/tgstation/tgstation-server-webpanel/issues/new")},d.createElement(x.G,{icon:A.eHv})))}}var V=n(3e3),q=n(15881),Y=n(86755);class X extends d.Component{constructor(e){super(e),this.state={}}componentDidUpdate(e){this.props.location.key!==e.location.key&&this.setState({error:void 0,errorInfo:void 0})}componentDidCatch(e,t){this.setState({error:e,errorInfo:t})}render(){return this.state.error?d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(q.Z,{className:"bg-transparent",border:"danger"},d.createElement(q.Z.Header,{className:"bg-danger"},d.createElement(h.Z,{id:"error.somethingwentwrong"})),d.createElement(q.Z.Body,null,d.createElement(q.Z.Title,null,this.state.error.name,": ",this.state.error.message),d.createElement(q.Z.Text,{as:"pre",className:"bg-transparent text-danger"},d.createElement("code",null,`Webpanel Version: ${Y.q4}\nWebpanel Mode: ${Y.IK}\nStack trace: ${this.state.errorInfo?.componentStack??"Unable to get stack info"}`))))):this.props.children}}var Q=(0,$.EN)(X),ee=n(20271),te=n(35855),ne=n(40684),re=n(11895),oe=n(97888);class se extends d.Component{constructor(e){super(e),this.state={clear:!1}}componentDidUpdate(e){this.state.clear?this.setState({clear:!1}):e.match.path==this.props.match.path&&e.location.key!=this.props.location.key&&this.props.location.state?.reload&&this.setState({clear:!0})}render(){return this.state.clear?"":this.props.children}}var ae=(0,$.EN)(se),ie=n(9310);const le=e=>d.createElement(te.Z,{text:"loading.page"},d.createElement(h.Z,{id:e})),ce=(0,ne.ZP)((()=>n.e(370).then(n.bind(n,56370))),{fallback:le("loading.page.notfound")});class de extends d.Component{constructor(e){super(e),this.refreshListener=this.refreshListener.bind(this);const t=new Map;D.Z.getImmediateRoutes(!1).forEach((e=>{t.set(e.name,(0,ne.ZP)((()=>n(66235)(`./${e.file}`)),{fallback:le(e.name)}))})),this.state={loading:!!new URLSearchParams(window.location.search).get("state"),routes:D.Z.getImmediateRoutes(!1),components:t}}refreshListener(e){this.setState({routes:e})}async componentDidMount(){D.Z.on("refreshAll",this.refreshListener),this.props.history.listen((e=>{this.listener(e.pathname)})),this.listener(this.props.location.pathname);const e=new URLSearchParams(window.location.search),t=e.get("state");if(!t)return void this.setState({loading:!1});window.history.replaceState(null,document.title,window.location.pathname);const n=JSON.parse(window.sessionStorage.getItem("oauth")??"{}")[t];if(!n)return this.setErrorAndEnd(new v.ZP(v.jK.LOGIN_BAD_OAUTH,{jsError:Error(`State(${t}) cannot be resolved to a provider.`)}));const r=e.get("code");if(!r)return this.setErrorAndEnd(new v.ZP(v.jK.LOGIN_BAD_OAUTH,{jsError:Error("Code not found.")}));this.props.history.replace(n.url);const o=await E.Z.login({type:re.P.OAuth,provider:n.provider,token:r});if(window.sessionStorage.removeItem("oauth"),o.code!==b.G.OK)return this.setErrorAndEnd(o.error);this.setState({loading:!1})}componentWillUnmount(){D.Z.removeListener("refreshAll",this.refreshListener)}setErrorAndEnd(e){B.Mq.oautherrors=[e],this.setState({loading:!1})}listener(e){const t=D.Z.getImmediateRoutes(!1);for(const n of t)if(n.category&&n.navbarLoose&&(0,_.JW)(e,n.route)){this.props.selectCategory(n.category);break}}render(){return this.state.loading?d.createElement(te.Z,{text:"loading.app"}):d.createElement(Q,null,d.createElement(ae,null,d.createElement("div",null,d.createElement($.rs,null,this.state.routes.map((e=>{if(e.loginless||this.props.loggedIn)return d.createElement($.AW,{exact:!e.loose,path:e.route,key:e.name,render:t=>{let n;return n=e.cachedAuth?this.state.components.get(e.name):oe.Z,this.context?.user||e.loginless?this.context?.serverInfo||e==B.$w.config?e.noContainer?d.createElement(d.Fragment,null,d.createElement(n,t)):d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(n,t)):d.createElement(m.Z,null,d.createElement(V.ZP,{error:new v.ZP(v.jK.APP_FAIL,{jsError:Error("Router has no server info in the general context")})})):d.createElement(m.Z,null,d.createElement(V.ZP,{error:new v.ZP(v.jK.APP_FAIL,{jsError:Error("Router has no user in the general context")})}))}})})),d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement($.AW,{key:"notfound"},this.props.loggedIn?d.createElement(ce,null):d.createElement(ie.default,{loggedOut:this.props.loggedOut})))))))}}de.contextType=T.f;var ue=(0,$.EN)(de);class me{}me.en="en";var he=me;class pe{constructor(e,t){this.locale=e,this.messages=t}}class fe{static getShortHandedLocale(e){return e.split("-")[0]}async loadTranslation(e){const t=await n(862)(`./${e}.json`);if(!t){let t=fe.getShortHandedLocale(e);if(t===e){if(t===fe.fallbackLocale)throw new Error("Invalid locale: "+e);t=fe.fallbackLocale}return await this.loadTranslation(t)}let r=null;try{r=new pe(e,t)}catch(t){throw Error(`Error loading localization for locale '${e}': ${JSON.stringify(t)}`)}return r}}fe.fallbackLocale=he.en;var ge=fe;class ve extends d.Component{constructor(e){super(e),this.state={}}componentDidMount(){document.title="TGS Webpanel v"+g,document.addEventListener("keydown",(e=>{"L"===e.key&&e.ctrlKey&&e.shiftKey&&(E.Z.logout(),E.Z.login(w.Z.default))}))}render(){return d.createElement(f.VK,{basename:window.publicPath?new URL(window.publicPath,window.location.href).pathname:Y.UG},d.createElement(Q,null,d.createElement(z,{category:this.state.passdownCat,loggedIn:this.props.loggedIn}),this.props.loading?d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(te.Z,{text:"loading.app"})):d.createElement(d.Fragment,null,d.createElement(m.Z,{className:"mt-5"},d.createElement(u.Z,{variant:"warning",className:"d-block d-lg-none"},d.createElement(u.Z.Heading,null,d.createElement(h.Z,{id:"warning.screensize.header"})),d.createElement("hr",null),d.createElement(h.Z,{id:"warning.screensize"})),Array.from(this.context.errors.values()).map(((e,t)=>d.createElement(V.ZP,{error:e,key:t,onClose:()=>this.context.deleteError(e)})))),d.createElement(ue,{loggedIn:this.props.loggedIn,loggedOut:this.props.loggedOut,selectCategory:e=>{this.setState({passdownCat:{name:e,key:Math.random().toString()}})}})),this.props.loggedIn?d.createElement(ee.Z,null):null),d.createElement(J,null),d.createElement(H,null))}}ve.contextType=T.f;class be extends d.Component{constructor(e){super(e),this.translationFactory=void 0,this.finishLogin=this.finishLogin.bind(this),this.finishLogout=this.finishLogout.bind(this),this.updateContextUser=this.updateContextUser.bind(this),this.updateContextServer=this.updateContextServer.bind(this),this.deleteGeneralContextError=this.deleteGeneralContextError.bind(this),this.translationFactory=this.props.translationFactory??new ge,this.state={loggedIn:!1,loggedOut:!1,loading:!0,GeneralContextInfo:{errors:new Set,user:null,serverInfo:null,deleteError:this.deleteGeneralContextError}}}async updateContextUser(){const e=await y.Z.getCurrentUser();e.code===b.G.OK?this.setState((t=>({GeneralContextInfo:{errors:t.GeneralContextInfo.errors,user:e.payload,serverInfo:t.GeneralContextInfo.serverInfo,deleteError:t.GeneralContextInfo.deleteError}}))):e.error.code===v.jK.HTTP_ACCESS_DENIED?this.setState((e=>({GeneralContextInfo:{user:null,serverInfo:e.GeneralContextInfo.serverInfo,deleteError:e.GeneralContextInfo.deleteError,errors:e.GeneralContextInfo.errors}}))):(setTimeout((()=>{this.updateContextUser()}),5e3),this.setState((t=>{const n=new Set(t.GeneralContextInfo.errors);return n.add(e.error),{GeneralContextInfo:{errors:n,deleteError:t.GeneralContextInfo.deleteError,user:null,serverInfo:t.GeneralContextInfo.serverInfo}}})))}async updateContextServer(e){const t=await E.Z.getServerInfo();t.code===b.G.OK?this.setState((n=>{const r=new Set(n.GeneralContextInfo.errors);return e&&r.delete(e),{GeneralContextInfo:{errors:r,user:n.GeneralContextInfo.user,serverInfo:t.payload,deleteError:n.GeneralContextInfo.deleteError}}})):(setTimeout((()=>{this.updateContextServer(t.error)}),5e3),this.setState((n=>{const r=new Set(n.GeneralContextInfo.errors);return r.add(t.error),e&&r.delete(e),{GeneralContextInfo:{errors:r,deleteError:n.GeneralContextInfo.deleteError,user:n.GeneralContextInfo.user,serverInfo:null}}})))}deleteGeneralContextError(e){this.setState((t=>{const n=new Set(t.GeneralContextInfo.errors);return n.delete(e),{GeneralContextInfo:{deleteError:t.GeneralContextInfo.deleteError,user:t.GeneralContextInfo.user,serverInfo:t.GeneralContextInfo.serverInfo,errors:n}}}))}finishLogin(){console.log("Logging in"),this.updateContextUser().then((()=>this.setState({loggedIn:!0,loading:!1})))}finishLogout(){this.setState({loggedIn:!1,loggedOut:!0}),this.updateContextUser()}async componentDidMount(){Z.Z.on("loginSuccess",this.finishLogin),E.Z.on("logout",this.finishLogout),E.Z.on("purgeCache",this.updateContextServer),E.Z.on("purgeCache",this.updateContextUser),await this.loadTranslation();const e=await E.Z.initApi();await this.updateContextServer(),e&&await this.updateContextUser(),this.setState({loading:!1,loggedIn:e})}componentWillUnmount(){Z.Z.removeListener("loginSuccess",this.finishLogin),E.Z.removeListener("logout",this.finishLogout),E.Z.removeListener("purgeCache",this.updateContextServer),E.Z.removeListener("purgeCache",this.updateContextUser)}render(){return this.state.translationError?d.createElement("p",{className:"App-error"},this.state.translationError):this.state.translation?d.createElement(p.Z,{locale:this.state.translation.locale,messages:this.state.translation.messages,defaultLocale:"en"},d.createElement(T.f.Provider,{value:this.state.GeneralContextInfo},d.createElement(ve,{loading:this.state.loading,loggedIn:this.state.loggedIn,loggedOut:this.state.loggedOut}))):d.createElement(te.Z,null,"Loading translations...")}async loadTranslation(){console.time("LoadTranslations");try{const e=await this.translationFactory.loadTranslation(this.props.locale);this.setState({translation:e})}catch(e){return void this.setState({translationError:JSON.stringify(e)??"An unknown error occurred"})}console.timeEnd("LoadTranslations")}}const Ee=d.createElement(d.StrictMode,null,d.createElement(be,{locale:he.en}));var ye=n(78947),we=n(51417),Ze=n(66370),Ie=n(83183),Ce=n(68055),Ae=n(24075),xe=n(47810),ke=n(57026),Se=n(20702),je=n(8792),Le=n(9752),Ne=n(19500),Oe=n(51257),Pe=n(16688),$e=n(68099),Ue=n(82414),Ge=n(84176),Re=n(62679),Te=n(609),_e=n(7371),De=n(40098),Be=n(16995),Fe=n(95337),Me=n(59986),ze=n(5702),Ke=n(91883),He=n(59545),We=n(55554),Je=n(42619),Ve=n(40864),qe=n(27879),Ye=n(46357),Xe=n(27593);ye.vI.add(Ce.LE,He.NB,Se.RL,qe.IL,Xe.wO,Oe.vc,Ke.UO,De.r8,Be.Ps,Pe.J9,We.Cg,xe.Kb,Ve.X7,$e.YH,Le._3,Ie.yO,Ge.DD,_e.Iw,we.zhw,we.omb,Je.$,Ue.sq,Ze.Pd,Ne.ne,Re.gf,ke.lX,je.cC,Ye.FV,Ae.mh,Fe.wn,Me.xf,ze.aC,Te.by,A.Kl4,A.tAh,Te.by,A.I4f,A.eW2,A.Mzg,A.olY,A.x58,A.Jvx,A.gMD,A.cwv,A.eHv,A.Yjj,A.acZ,Ie.yO,A.cf$,A.l9D),o.Z.loadconfig(),s.Z.init(),window.loadedChannelFromWebpack&&"DEV"!==Y.IK&&alert("Warning: channel.json was served from bundled files instead of TGS, the webpanel is running from the local version instead of the github update repo.\nPlease report this to your server host.\nIf you are the server host, please report this to alexkar598#2712 on discord\n\nWebpanel version: "+Y.q4);try{window.localStorage.removeItem("username"),window.sessionStorage.removeItem("username"),window.localStorage.removeItem("password"),window.sessionStorage.removeItem("password")}catch{}function Qe(){r.render(Ee,document.getElementById("root"))}window.addEventListener("DOMContentLoaded",Qe),"interactive"!==document.readyState&&"complete"!==document.readyState||Qe()},70601:function(e,t,n){"use strict";var r=n(12527),o=n(50452),s=n(1320);class a extends r.TypedEmitter{constructor(){super(),this.refreshing=!1,window.rtcontroller=this,this.refreshRoutes=this.refreshRoutes.bind(this),o.Z.addHook(this.refreshRoutes),this.refreshRoutes().catch(console.error),console.time("Category mapping");const e=new Map;for(const[t,n]of Object.entries(s.XT))n.routes=[],e.set(n.name,n),s.Nz[t]=n;for(const t of Object.values(s.$w)){if(!t.category)continue;const n=e.get(t.category);if(n){if(n.routes.push(t),t.catleader){if(n.leader){console.error("Category has two leaders",n.leader,t);continue}n.leader=t}}else console.error("Route has invalid category",t)}console.log("Categories mapped",e),console.timeEnd("Category mapping")}async refreshRoutes(){if(this.refreshing)return void console.log("Already refreshing");this.refreshing=!0;const e=[],t=this.getImmediateRoutes(!1);for(const n of t)n.cachedAuth=void 0,n.isAuthorized?e.push(n.isAuthorized().then((e=>{n.cachedAuth=e}))):n.cachedAuth=!0;await Promise.all(e),this.emit("refresh",this.getImmediateRoutes(!0));const n=this.getImmediateRoutes(!1);return this.emit("refreshAll",n),this.refreshing=!1,console.log("Routes refreshed",n),await this.getRoutes()}wait4refresh(){return new Promise((e=>{this.refreshing?this.on("refresh",(()=>{e()})):e()}))}async getRoutes(e=!0){return await this.wait4refresh(),this.getImmediateRoutes(e)}getImmediateRoutes(e=!0){const t=[];for(const n of Object.values(s.$w))n.isAuthorized&&!n.cachedAuth&&e||t.push(n);return t}}t.Z=new a},16964:function(e,t,n){"use strict";n.d(t,{$A:function(){return p},$V:function(){return m},D0:function(){return d},DB:function(){return u},H5:function(){return v},JW:function(){return a},JY:function(){return f},Kp:function(){return g},LR:function(){return o},N3:function(){return c},Zg:function(){return i},ko:function(){return s},mg:function(){return h}});var r=n(51068);function o(e,t){const n=document.createElement("a");n.setAttribute("href","data:text/plain;charset=utf-8,"+encodeURIComponent(t)),n.setAttribute("download",e),n.style.display="none",document.body.appendChild(n),n.click(),document.body.removeChild(n)}function s(e,t,n,r){return e.replace(new RegExp(t.replace(/([/,!\\^${}[\]().*+?|<>\-&])/g,"\\$&"),r?"gi":"g"),n.replace(/\$/g,"$$$$"))}function a(e,t,n=!1){return"/"===e.slice(-1)&&(e=e.slice(0,-1)),"/"===t.slice(-1)&&(t=t.slice(0,-1)),(0,r.Bo)(t,void 0,{end:n}).test(e)}function i(e){return e.permissionSet??e.group?.permissionSet}function l(e,t){return!!(t&e)}function c(e,t){return l(e.administrationRights,t)}function d(e,t){return l(e.instanceManagerRights,t)}function u(e,t){return l(e.engineRights,t)}function m(e,t){return l(e.chatBotRights,t)}function h(e,t){return l(e.dreamDaemonRights,t)}function p(e,t){return l(e.dreamMakerRights,t)}function f(e,t){return l(e.instancePermissionSetRights,t)}function g(e,t){return l(e.repositoryRights,t)}function v(e,t){return l(e.configurationRights,t)}},1320:function(e,t,n){"use strict";n.d(t,{$w:function(){return d},Mq:function(){return h},Nz:function(){return m},XT:function(){return u}});var r=n(48509),o=n(53803),s=n(16942),a=n(42522),i=n(16964);function l(e){return async()=>{if(!a.Z.hasToken())return!1;const t=await s.Z.getCurrentUser();return t.code==o.G.OK&&!!((0,i.Zg)(t.payload).administrationRights&e)}}function c(e){return async()=>{if(!a.Z.hasToken())return!1;const t=await s.Z.getCurrentUser();return t.code==o.G.OK&&!!((0,i.Zg)(t.payload).instanceManagerRights&e)}}const d={home:{name:"routes.home",route:"/",file:"Home",loose:!1,navbarLoose:!1,visibleNavbar:!0,homeIcon:void 0,category:"home",catleader:!0},instancecreate:{name:"routes.instancecreate",route:"/instances/create",file:"Instance/Create",loose:!1,navbarLoose:!1,isAuthorized:c(r.c2.Create),visibleNavbar:!1,category:"instance",catleader:!1},instancelist:{name:"routes.instancelist",route:"/instances/",file:"Instance/List",loose:!1,navbarLoose:!0,isAuthorized:c(r.c2.List|r.c2.Read),visibleNavbar:!0,homeIcon:"hdd",category:"instance",catleader:!0},instanceedit:{name:"routes.instanceedit",route:"/instances/edit/:id(\\d+)/:tab?/",file:"Instance/InstanceEdit",get link(){return void 0!==h.selectedinstanceid?`/instances/edit/${h.selectedinstanceid}/${void 0!==h.selectedinstanceedittab?`${h.selectedinstanceedittab}/`:""}`:d.instancelist.link??d.instancelist.route},loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"instance"},instancejobs:{name:"routes.instancejobs",route:"/instances/jobs/",file:"Instance/Jobs",loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"instance"},userlist:{name:"routes.usermanager",route:"/users/",file:"User/List",loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:"user",category:"user",catleader:!0},useredit:{name:"routes.useredit",route:"/users/edit/user/:id(\\d+)/:tab?/",get link(){return void 0!==h.selecteduserid?`/users/edit/user/${h.selecteduserid}/${void 0!==h.selectedusertab?`${h.selectedusertab}/`:""}`:d.userlist.link??d.userlist.route},file:"User/Edit",loose:!0,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"user"},usercreate:{name:"routes.usercreate",route:"/users/create/",link:"/users/create/",file:"User/Create",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.WriteUsers),visibleNavbar:!0,homeIcon:void 0,category:"user"},admin:{name:"routes.admin",route:"/admin/",file:"Administration",loose:!1,navbarLoose:!0,isAuthorized:l(r.oj.ChangeVersion|r.oj.DownloadLogs|r.oj.UploadVersion),visibleNavbar:!0,homeIcon:"tools",category:"admin",catleader:!0},admin_update:{name:"routes.admin.update",route:"/admin/update/:all?/",file:"Admin/Update",link:"/admin/update/",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.ChangeVersion|r.oj.UploadVersion),visibleNavbar:!0,homeIcon:void 0,category:"admin"},admin_logs:{name:"routes.admin.logs",route:"/admin/logs/:name?/",link:"/admin/logs/",file:"Admin/Logs",loose:!1,navbarLoose:!0,isAuthorized:l(r.oj.DownloadLogs),visibleNavbar:!0,homeIcon:void 0,category:"admin",noContainer:!0},passwd:{name:"routes.passwd",route:"/users/passwd/:id(\\d+)?/",link:"/users/passwd/",file:"ChangePassword",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.EditOwnPassword),visibleNavbar:!1,homeIcon:"key"},config:{name:"routes.config",route:"/config/",file:"Configuration",loose:!0,navbarLoose:!0,loginless:!0,visibleNavbar:!1,homeIcon:"cogs"},setup:{name:"routes.setup",route:"/setup/",file:"Setup",loose:!0,navbarLoose:!0,loginless:!0,visibleNavbar:!1},oAuth:{name:"routes.oauth",route:"/oauth/:provider?/",file:"Login",loose:!0,navbarLoose:!1,loginless:!0,visibleNavbar:!1},info:{name:"routes.info",route:"/info",file:"Info",loose:!1,navbarLoose:!1,loginless:!0,visibleNavbar:!0,homeIcon:"info-circle",category:void 0,catleader:!1}},u={home:{name:"home"},instance:{name:"instance"},user:{name:"user"},admin:{name:"admin"}},m={},h={selecteduserid:void 0,selectedusertab:void 0,selectedinstanceid:void 0,selectedinstanceedittab:void 0,instancelistpage:void 0,loglistpage:void 0,byondlistpage:void 0,userlistpage:void 0,jobhistorypage:new Map,oautherrors:[]}},39087:function(e,t,n){"use strict";var r=n(94015),o=n.n(r),s=n(23645),a=n.n(s),i=n(61667),l=n.n(i),c=n(31100),d=a()(o()),u=l()(c);d.push([e.id,".App {\n background-size: 50%;\n background: #1e1e1e url("+u+") no-repeat center;\n position: absolute;\n width: 100%;\n top: 0;\n bottom: 0;\n display: grid;\n}\n\n.App-error {\n color: red;\n font-size: 150%;\n margin: auto;\n}\n\n.App-main {\n display: grid;\n}\n\n.Root {\n overflow: hidden;\n display: grid;\n grid-template-rows: 9% auto;\n}\n\n.Root-login {\n display: grid;\n}\n","",{version:3,sources:["webpack://./src/App.css"],names:[],mappings:"AAAA;IACI,oBAAoB;IACpB,4EAAkD;IAClD,kBAAkB;IAClB,WAAW;IACX,MAAM;IACN,SAAS;IACT,aAAa;AACjB;;AAEA;IACI,UAAU;IACV,eAAe;IACf,YAAY;AAChB;;AAEA;IACI,aAAa;AACjB;;AAEA;IACI,gBAAgB;IAChB,aAAa;IACb,2BAA2B;AAC/B;;AAEA;IACI,aAAa;AACjB",sourcesContent:[".App {\n background-size: 50%;\n background: #1e1e1e url(logo.svg) no-repeat center;\n position: absolute;\n width: 100%;\n top: 0;\n bottom: 0;\n display: grid;\n}\n\n.App-error {\n color: red;\n font-size: 150%;\n margin: auto;\n}\n\n.App-main {\n display: grid;\n}\n\n.Root {\n overflow: hidden;\n display: grid;\n grid-template-rows: 9% auto;\n}\n\n.Root-login {\n display: grid;\n}\n"],sourceRoot:""}]),t.Z=d},58007:function(e,t,n){"use strict";var r=n(94015),o=n.n(r),s=n(23645),a=n.n(s)()(o());a.push([e.id,".tgs-update-notification {\n color: #66ff07;\n}\n","",{version:3,sources:["webpack://./src/components/AppNavbar.css"],names:[],mappings:"AAAA;IACI,cAAc;AAClB",sourcesContent:[".tgs-update-notification {\n color: #66ff07;\n}\n"],sourceRoot:""}]),t.Z=a},66235:function(e,t,n){var r={"./Admin/Logs":[43408,171,408],"./Admin/Logs.tsx":[43408,171,408],"./Admin/Update":[80732,171,578,6,724,732],"./Admin/Update.tsx":[80732,171,578,6,724,732],"./Administration":[29363,171,363],"./Administration.tsx":[29363,171,363],"./ChangePassword":[61304,799],"./ChangePassword.tsx":[61304,799],"./Configuration":[67671,671],"./Configuration.tsx":[67671,671],"./Home":[59638,638],"./Home.tsx":[59638,638],"./Info":[41051,171,51],"./Info.tsx":[41051,171,51],"./Instance/Create":[38747,899,856,637,756,611,578,6,747],"./Instance/Create.tsx":[38747,899,856,637,756,611,578,6,747],"./Instance/Edit/ChatBots":[90740,767,171,740,318],"./Instance/Edit/ChatBots.tsx":[90740,767,171,740,318],"./Instance/Edit/Config":[62685,171,685],"./Instance/Edit/Config.tsx":[62685,171,685],"./Instance/Edit/Deployment":[44298,899,856,171,578,240,67,356],"./Instance/Edit/Deployment.tsx":[44298,899,856,171,578,240,67,356],"./Instance/Edit/Engine":[32240,899,171,578,240,657],"./Instance/Edit/Engine.tsx":[32240,899,171,578,240,657],"./Instance/Edit/Files":[20926,637,171,926,608],"./Instance/Edit/Files.tsx":[20926,637,171,926,608],"./Instance/Edit/InstancePermissions":[87345,803,171,345,246],"./Instance/Edit/InstancePermissions.tsx":[87345,803,171,345,246],"./Instance/Edit/JobHistory":[25921,171,921],"./Instance/Edit/JobHistory.tsx":[25921,171,921],"./Instance/Edit/Repository":[18264,856,611,171,578,757,264,233],"./Instance/Edit/Repository.tsx":[18264,856,611,171,578,757,264,233],"./Instance/Edit/Server":[86046,899,756,171,578,240,67,792],"./Instance/Edit/Server.tsx":[86046,899,756,171,578,240,67,792],"./Instance/InstanceEdit":[9182,899,856,637,756,611,803,767,171,578,757,165,240,67,264,740,926,345,182],"./Instance/InstanceEdit.tsx":[9182,899,856,637,756,611,803,767,171,578,757,165,240,67,264,740,926,345,182],"./Instance/Jobs":[41818,818],"./Instance/Jobs.tsx":[41818,818],"./Instance/List":[70670,171,670],"./Instance/List.tsx":[70670,171,670],"./Login":[9310],"./Login.tsx":[9310],"./Setup":[12757,666],"./Setup.tsx":[12757,666],"./User/Create":[14898,898],"./User/Create.tsx":[14898,898],"./User/Edit":[11404,803,171,404],"./User/Edit.tsx":[11404,803,171,404],"./User/List":[8746,171,746],"./User/List.tsx":[8746,171,746]};function o(e){if(!n.o(r,e))return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=r[e],o=t[0];return Promise.all(t.slice(1).map(n.e)).then((function(){return n(o)}))}o.keys=function(){return Object.keys(r)},o.id=66235,e.exports=o},862:function(e,t,n){var r={"./en.json":[2422,422]};function o(e){if(!n.o(r,e))return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=r[e],o=t[0];return n.e(t[1]).then((function(){return n.t(o,19)}))}o.keys=function(){return Object.keys(r)},o.id=862,e.exports=o},31100:function(e){"use strict";e.exports="https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/835dd74f1c08a7f91a60.svg"}},a={};function i(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={id:e,exports:{}};return s[e].call(n.exports,n,n.exports,i),n.exports}i.m=s,e=[],i.O=function(t,n,r,o){if(!n){var s=1/0;for(d=0;d=o)&&Object.keys(i.O).every((function(e){return i.O[e](n[l])}))?n.splice(l--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},i.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);i.r(o);var s={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){s[t]=function(){return e[t]}}));return s.default=function(){return e},i.d(o,s),o},i.d=function(e,t){for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.f={},i.e=function(e){return Promise.all(Object.keys(i.f).reduce((function(t,n){return i.f[n](e,t),t}),[]))},i.u=function(e){return e+"."+{6:"8be2a144a2793570315b",51:"018c826056e10797488e",67:"d56fce4f5250e06063bf",165:"ba83e5a7e5ce7342f32c",171:"91936e72280a3d6afb03",182:"863e8e49ed9d42931724",233:"bca5af88bf85dd9388cf",240:"9d4d9c96f62ce48cce5f",246:"67f9678139a3c824ae5c",264:"6bc80619e126b48208c1",318:"262f7ca0e51a961f2197",345:"463f4581d9005d5a1f8f",356:"8162c10f9d74465626fb",363:"d935e8c7446b49ba6955",370:"d23700bdb9e4969823c6",404:"a8f6c9632768de417c6c",408:"67a24726289357584bda",422:"6ffa6fbbf00a7aa4137e",578:"b30e90acaefb17f3a915",608:"cdba29b0c970b4473f8d",611:"9c4e031ea3c5676ddc02",637:"67f97d250d5cc7c88b41",638:"22ae8c317d679c0fbe03",657:"ebfcf366f770219cda87",666:"466319a2ec0bfe75619b",670:"f23545dfb1fa34187665",671:"d70d3c2a0ea3e94cdaa3",685:"d842923a8b4fb2925914",724:"37608abcbb4a745b53ee",732:"26d1b1972e8409d84e2b",740:"ff042fc269d749115529",746:"24e79402e2a97d9d230f",747:"a7a7de444f0f31df471a",756:"3ea7efa9992f327c4f5e",757:"cd59822a9cafc5472de2",767:"2226708e286478875491",792:"d0502c03902c2b81a3bc",799:"5f592f7ea70157f79ee3",803:"e84dc0cafa5ef825835a",818:"de10c00448eedd21537d",856:"4c7ce7828aeda2beaebd",898:"506fa6125431a4f5701d",899:"40a4d85a43cf77edbe66",921:"d5768a750d900f116d8c",926:"9721f261bd7f5a7449df"}[e]+".bundle.js"},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="tgstation-server-control-panel:",i.l=function(e,t,n,s){if(r[e])r[e].push(t);else{var a,l;if(void 0!==n)for(var c=document.getElementsByTagName("script"),d=0;d 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var getProto = Object.getPrototypeOf ? function(obj) { return Object.getPrototypeOf(obj); } : function(obj) { return obj.__proto__; };\nvar leafPrototypes;\n// create a fake namespace object\n// mode & 1: value is a module id, require it\n// mode & 2: merge all properties of value into the ns\n// mode & 4: return value when already ns object\n// mode & 16: return value when it's Promise-like\n// mode & 8|1: behave like require\n__webpack_require__.t = function(value, mode) {\n\tif(mode & 1) value = this(value);\n\tif(mode & 8) return value;\n\tif(typeof value === 'object' && value) {\n\t\tif((mode & 4) && value.__esModule) return value;\n\t\tif((mode & 16) && typeof value.then === 'function') return value;\n\t}\n\tvar ns = Object.create(null);\n\t__webpack_require__.r(ns);\n\tvar def = {};\n\tleafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];\n\tfor(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {\n\t\tObject.getOwnPropertyNames(current).forEach(function(key) { def[key] = function() { return value[key]; }; });\n\t}\n\tdef['default'] = function() { return value; };\n\t__webpack_require__.d(ns, def);\n\treturn ns;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"tgstation-server-control-panel:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = function(url, done, key, chunkId) {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 15;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = function(prev, event) {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach(function(fn) { return fn(event); });\n\t\tif(prev) return prev(event);\n\t};\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 15000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","import React, { ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\nimport GenericAlert from \"./GenericAlert\";\n\ninterface IProps extends RouteComponentProps {}\n\ninterface IState {\n auth: boolean;\n}\n\nclass AccessDenied extends React.Component {\n public render(): ReactNode {\n const goBack = () => {\n this.props.history.goBack();\n };\n return (\n \n \n \n );\n }\n}\n\nexport default withRouter(AccessDenied);\n","import ClickToSelect from \"@mapbox/react-click-to-select\";\nimport React, { Component, ReactNode } from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport Button from \"react-bootstrap/Button\";\nimport Modal from \"react-bootstrap/Modal\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport InternalError, {\n DescType,\n ErrorCode\n} from \"../../ApiClient/models/InternalComms/InternalError\";\nimport { API_VERSION, MODE, VERSION } from \"../../definitions/constants\";\n\ninterface IProps {\n error: InternalError | undefined;\n onClose?: () => void;\n}\n\ninterface IState {\n popup: boolean;\n}\n\nclass ErrorAlert extends Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {\n popup: false\n };\n }\n public render(): ReactNode {\n if (!this.props.error) {\n return \"\";\n }\n\n const handleClose = () => this.setState({ popup: false });\n const handleOpen = () => this.setState({ popup: true });\n\n return (\n \n \n
\n\n \n\n \n \n \n \n \n \n \n {this.props.error.desc?.type === DescType.LOCALE ? (\n \n ) : this.props.error.desc?.desc ? (\n this.props.error.desc.desc\n ) : (\n \"\"\n )}\n
\n \n \n {`Webpanel Version: ${VERSION}\nWebpanel Mode: ${MODE}\nAPI Version: ${API_VERSION}\n\nError Code: ${this.props.error.code}\nError Description: ${this.props.error.desc ? this.props.error.desc.desc : \"No description\"}\n\nAdditional Information:\n${this.props.error.extendedInfo}`.replace(/\\\\/g, \"\\\\\\\\\")}\n \n \n
\n \n \n \n \n \n \n
\n \n );\n }\n}\n\nexport default ErrorAlert;\n\nexport type ErrorState = [\n Array | undefined>,\n React.Dispatch | undefined>>>\n];\n\nfunction addError([, setErrors]: ErrorState, error: InternalError): void {\n setErrors(prevState => {\n const errors = Array.from(prevState);\n errors.push(error);\n return errors;\n });\n}\n\nfunction displayErrors([errors, setErrors]: ErrorState): Array {\n return errors.map((err, index) => {\n if (!err) return;\n return (\n \n setErrors(prev => {\n const newarr = Array.from(prev);\n newarr[index] = undefined;\n return newarr;\n })\n }\n />\n );\n });\n}\n\nexport { displayErrors, addError };\n","import React from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport { FormattedMessage } from \"react-intl\";\n\ninterface IProps {\n title: string;\n body?: string;\n children?: JSX.Element;\n}\n\nexport default function GenericAlert(props: IProps): JSX.Element {\n return (\n \n \n {props.body ? (\n \n
\n \n
\n ) : props.children ? (\n \n
\n {props.children}\n
\n ) : null}\n
\n );\n}\n","import ClickToSelect from \"@mapbox/react-click-to-select\";\nimport React, { useState } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport Modal from \"react-bootstrap/Modal\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport { ErrorCode as TGSErrorCode } from \"../../ApiClient/generatedcode/generated\";\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\n\ninterface IProps {\n job: TGSJobResponse;\n}\n\nexport default function JobError(props: IProps): JSX.Element {\n const [open, setOpen] = useState(false);\n return (\n <>\n setOpen(true)}\n size=\"sm\">\n \n \n\n setOpen(false)} size=\"lg\">\n \n \n \n \n \n \n :{\" \"}\n {props.job.errorCode !== undefined && props.job.errorCode !== null\n ? TGSErrorCode[props.job.errorCode]\n : \"NoCode\"}\n
\n \n \n {props.job.exceptionDetails}\n \n \n
\n \n \n \n
\n \n );\n}\n","import { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport OverlayTrigger from \"react-bootstrap/OverlayTrigger\";\nimport ProgressBar from \"react-bootstrap/ProgressBar\";\nimport Toast from \"react-bootstrap/Toast\";\nimport ToastBody from \"react-bootstrap/ToastBody\";\nimport ToastHeader from \"react-bootstrap/ToastHeader\";\nimport Tooltip from \"react-bootstrap/Tooltip\";\nimport { FormattedMessage, FormattedRelativeTime } from \"react-intl\";\n\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\nimport JobError from \"./JobError\";\n\ninterface IState {}\ninterface IProps {\n job: TGSJobResponse;\n width?: string;\n onClose?: (job: TGSJobResponse) => void;\n onCancel: (job: TGSJobResponse) => void;\n}\n\nexport default class JobCard extends React.Component {\n public render(): ReactNode {\n const job = this.props.job;\n const createddate = new Date(job.startedAt);\n const createddiff = (createddate.getTime() - Date.now()) / 1000;\n const stoppeddate = new Date(job.stoppedAt ?? 0);\n const stoppeddiff = (stoppeddate.getTime() - Date.now()) / 1000;\n const variant =\n job.errorCode !== undefined || job.exceptionDetails !== undefined\n ? \"danger\"\n : job.cancelled\n ? \"warning\"\n : job.stoppedAt\n ? \"success\"\n : \"info\";\n\n return (\n {\n if (this.props.onClose) this.props.onClose(job);\n }}>\n \n #{job.id}: {job.description}\n \n \n {/*STAGE*/}\n {job.stage ?
â–¶{job.stage}
: null}\n {/*STARTED AT*/}\n \n \n {createddate.toLocaleString()}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }>\n \n \n )}\n \n
\n\n {/*CREATED BY*/}\n \n \n \n {job.startedBy.id}\n \n }>\n {({ ref, ...triggerHandler }) => (\n } {...triggerHandler}>\n {job.startedBy.name}\n \n )}\n \n
\n
\n {/*STOPPED AT*/}\n {job.stoppedAt ? (\n \n \n \n {createddate.toLocaleString()}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }>\n \n \n )}\n \n
\n
\n ) : (\n \"\"\n )}\n {job.cancelledBy ? (\n \n \n \n \n {job.startedBy.id}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }\n {...triggerHandler}>\n {job.cancelledBy!.name}\n \n )}\n \n
\n
\n ) : (\n \"\"\n )}\n\n {/*ERROR*/}\n {job.errorCode !== undefined || job.exceptionDetails !== undefined ? (\n \n ) : (\n \"\"\n )}\n
\n \n {job.canCancel && !job.stoppedAt ? (\n this.props.onCancel(job)}>\n \n \n ) : null}\n
\n
\n \n );\n }\n}\n","import { faTimes } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ReactNode } from \"react\";\nimport { Button, Card } from \"react-bootstrap\";\nimport { OverlayInjectedProps } from \"react-bootstrap/Overlay\";\nimport OverlayTrigger from \"react-bootstrap/OverlayTrigger\";\nimport Tooltip from \"react-bootstrap/Tooltip\";\nimport { FormattedMessage } from \"react-intl\";\nimport { Rnd } from \"react-rnd\";\n\nimport type { InstanceResponse } from \"../../ApiClient/generatedcode/generated\";\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\nimport InternalError, { ErrorCode } from \"../../ApiClient/models/InternalComms/InternalError\";\nimport configOptions, { jobsWidgetOptions } from \"../../ApiClient/util/config\";\nimport JobsController from \"../../ApiClient/util/JobsController\";\nimport ErrorAlert from \"./ErrorAlert\";\nimport JobCard from \"./JobCard\";\nimport Loading from \"./Loading\";\n\ninterface IProps {\n width?: string;\n widget: boolean;\n}\n\ninterface IState {\n jobs: Map>;\n errors: InternalError[];\n nextRetrySeconds: number | null;\n ownerrors: Array | undefined>;\n loading: boolean;\n instances: Map;\n}\n\nexport default class JobsList extends React.Component {\n public static defaultProps = {\n widget: true\n };\n\n private widgetRef = React.createRef();\n\n public constructor(props: IProps) {\n super(props);\n\n this.handleUpdate = this.handleUpdate.bind(this);\n this.onCancel = this.onCancel.bind(this);\n this.onClose = this.onClose.bind(this);\n\n this.state = {\n jobs: JobsController.jobsByInstance,\n errors: [],\n nextRetrySeconds: null,\n ownerrors: [],\n loading: true,\n instances: new Map()\n };\n }\n\n private addError(error: InternalError): void {\n this.setState(prevState => {\n const ownerrors = Array.from(prevState.ownerrors);\n ownerrors.push(error);\n if (this.widgetRef.current) {\n this.widgetRef.current.scrollTop = 0;\n }\n return {\n ownerrors\n };\n });\n }\n\n public componentDidMount(): void {\n JobsController.on(\"jobsLoaded\", this.handleUpdate);\n this.handleUpdate();\n }\n\n public componentWillUnmount(): void {\n JobsController.removeListener(\"jobsLoaded\", this.handleUpdate);\n }\n\n private currentTimeout?: NodeJS.Timeout | null;\n\n public handleUpdate(): void {\n if (this.currentTimeout) {\n clearTimeout(this.currentTimeout);\n this.currentTimeout = null;\n }\n\n let nextRetrySeconds;\n if (JobsController.nextRetry) {\n if (JobsController.nextRetry.getSeconds() > new Date().getSeconds()) {\n nextRetrySeconds = JobsController.nextRetry.getSeconds() - new Date().getSeconds();\n } else {\n nextRetrySeconds = 0;\n }\n this.currentTimeout = setTimeout(() => this.handleUpdate(), 1000);\n } else {\n nextRetrySeconds = null;\n }\n\n this.setState({\n jobs: JobsController.jobsByInstance,\n errors: JobsController.errors,\n nextRetrySeconds,\n loading: false,\n instances: JobsController.accessibleInstances\n });\n }\n\n private async onCancel(job: TGSJobResponse) {\n const status = await JobsController.cancelJob(job.id, error => this.addError(error));\n\n if (!status) {\n return;\n }\n JobsController.fastmode = 5;\n }\n\n private onClose(job: TGSJobResponse) {\n JobsController.clearJob(job.id);\n }\n\n public render(): ReactNode {\n if (!this.props.widget) return this.nested();\n\n let totalJobs = 0;\n for (const job of this.state.jobs.values()) {\n totalJobs += job.size;\n }\n\n let display: boolean;\n if (configOptions.jobswidgetdisplay.value === jobsWidgetOptions.NEVER) {\n display = false;\n } else if (configOptions.jobswidgetdisplay.value === jobsWidgetOptions.ALWAYS) {\n display = true;\n } else {\n display = totalJobs > 0 || this.state.errors.length > 0;\n }\n\n return (\n \n \n
\n
\n \n
\n {this.nested()}\n
\n \n \n );\n }\n\n private nested(): ReactNode {\n return (\n
\n {this.state.loading ? : \"\"}\n {this.state.ownerrors.map((err, index) => {\n if (!err) return;\n return (\n \n this.setState(prev => {\n const newarr = Array.from(prev.ownerrors);\n newarr[index] = undefined;\n return {\n ownerrors: newarr\n };\n })\n }\n />\n );\n })}\n {this.state.errors.length > 0 ? (\n \n {this.state.errors.map((error, index) => {\n return (\n \n \n
\n );\n })}\n \n {this.state.nextRetrySeconds === 0 ? (\n \n ) : this.state.nextRetrySeconds != null ? (\n
\n ) : (\n \n )}\n \n \n ) : null}\n {Array.from(this.state.jobs)\n .sort((a, b) => a[0] - b[0])\n .map(([instanceid, jobMap]) => {\n const renderTooltip = (instanceid: number) => {\n return (props: OverlayInjectedProps) => (\n \n {instanceid}\n \n );\n };\n\n let xFinishedEnabled = false;\n jobMap.forEach(job => {\n if (job.stoppedAt) xFinishedEnabled = true;\n });\n\n const instanceHeaderStyle = xFinishedEnabled\n ? { marginTop: \"5px\", marginLeft: \"20px\" }\n : undefined;\n\n return (\n \n
\n
\n
\n \n \n {this.state.instances.get(instanceid)?.name ??\n \"Unknown\"}{\" \"}\n (\n \n )\n \n \n
\n
\n {xFinishedEnabled ? (\n
\n (\n \n \n \n )}>\n \n jobMap.forEach(job => {\n if (job.stoppedAt)\n JobsController.clearJob(job.id);\n })\n }\n className=\"nowrap\">\n \n \n \n
\n ) : (\n \n )}\n
\n {Array.from(jobMap, ([, job]) => job)\n .sort((a, b) => b.id - a.id)\n .map(job => (\n \n ))}\n
\n );\n })}\n \n );\n }\n}\n","import React, { ReactNode } from \"react\";\nimport Spinner, { SpinnerProps } from \"react-bootstrap/Spinner\";\nimport { FormattedMessage } from \"react-intl\";\nimport CSSTransition from \"react-transition-group/CSSTransition\";\nimport TransitionGroup from \"react-transition-group/TransitionGroup\";\n\ntype IProps = SpinnerProps & {\n animation: \"border\" | \"grow\";\n center: boolean;\n width: number;\n widthUnit: string;\n className?: string;\n text?: string;\n};\n\ninterface IState {}\n\nexport default class Loading extends React.Component {\n public static defaultProps = {\n animation: \"border\",\n width: \"50\",\n widthUnit: \"vmin\",\n center: true\n };\n public constructor(props: IProps) {\n super(props);\n }\n\n public render(): ReactNode {\n const {\n variant,\n animation,\n center,\n className,\n width,\n widthUnit,\n text,\n children,\n ...otherprops\n } = this.props;\n const styles: React.CSSProperties = {\n width: `${width}${widthUnit}`,\n height: `${width}${widthUnit}`\n } as React.CSSProperties;\n return (\n \n {\n node.addEventListener(\"transitionend\", done, false);\n }}>\n
\n \n {text ? : \"\"}\n {children}\n
\n \n
\n );\n }\n}\n","import { faInvision } from \"@fortawesome/free-brands-svg-icons\";\nimport { faDiscord } from \"@fortawesome/free-brands-svg-icons/faDiscord\";\nimport { faGithub } from \"@fortawesome/free-brands-svg-icons/faGithub\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ChangeEvent, FormEvent, ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport Col from \"react-bootstrap/Col\";\nimport Card from \"react-bootstrap/esm/Card\";\nimport Form from \"react-bootstrap/Form\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps } from \"react-router\";\nimport { withRouter } from \"react-router-dom\";\n\nimport { OAuthProvider } from \"../../ApiClient/generatedcode/generated\";\nimport { CredentialsType } from \"../../ApiClient/models/ICredentials\";\nimport InternalError, { ErrorCode } from \"../../ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"../../ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"../../ApiClient/ServerClient\";\nimport CredentialsProvider from \"../../ApiClient/util/CredentialsProvider\";\nimport { GeneralContext, UnsafeGeneralContext } from \"../../contexts/GeneralContext\";\nimport { MODE } from \"../../definitions/constants\";\nimport KeycloakLogo from \"../../images/keycloak_icon_64px.png\";\nimport TGLogo from \"../../images/tglogo-white.svg\";\nimport { RouteData } from \"../../utils/routes\";\nimport ErrorAlert from \"../utils/ErrorAlert\";\nimport Loading from \"../utils/Loading\";\n\ninterface IProps extends RouteComponentProps {\n loggedOut: boolean;\n}\ninterface IState {\n busy: boolean;\n validated: boolean;\n username: string;\n password: string;\n errors: Array | undefined>;\n redirectSetup?: boolean;\n}\n\nexport type StoredOAuthData = { provider: OAuthProvider; url: string };\nexport type OAuthStateStorage = Record;\n\nclass Login extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: IProps) {\n super(props);\n this.submit = this.submit.bind(this);\n\n console.log(RouteData.oautherrors);\n\n this.state = {\n busy: false,\n validated: false,\n username: \"\",\n password: \"\",\n errors: Array.from(RouteData.oautherrors)\n };\n }\n\n public async componentDidMount(): Promise {\n const oauthState =\n window.sessionStorage.getItem(\"oauth\") ??\n CredentialsProvider.credentials?.type === CredentialsType.OAuth;\n if (!oauthState && (MODE === \"PROD\" || MODE === \"GITHUB\")) {\n // noinspection ES6MissingAwait\n await this.tryLoginDefault();\n }\n }\n\n private async tryLoginDefault(): Promise {\n if (this.props.loggedOut) {\n return;\n }\n\n const response = await ServerClient.login(CredentialsProvider.default);\n\n if (response.code === StatusCode.OK) {\n this.setState({\n redirectSetup: true\n });\n }\n }\n\n private addError(error: InternalError): void {\n this.setState(prevState => {\n const errors = Array.from(prevState.errors);\n errors.push(error);\n return {\n errors\n };\n });\n }\n\n public render(): ReactNode {\n const handleUsrInput = (event: ChangeEvent) =>\n this.setState({ username: event.target.value });\n const handlePwdInput = (event: ChangeEvent) =>\n this.setState({ password: event.target.value });\n\n if (this.state.busy || CredentialsProvider.hasToken()) {\n return ;\n }\n\n if (!this.context.serverInfo) {\n return ;\n }\n\n const providers: Record = {\n [OAuthProvider.GitHub]: ,\n [OAuthProvider.Discord]: (\n \n ),\n [OAuthProvider.TGForums]: \"tglogo\",\n [OAuthProvider.Keycloak]: (\n \"keycloaklogo\"\n ),\n [OAuthProvider.InvisionCommunity]: (\n \n )\n };\n\n const providersTheme: Record = {\n GitHub: \"#161b22\",\n Discord: \"#7289da\",\n TGForums: undefined,\n Keycloak: undefined,\n InvisionCommunity: undefined\n };\n\n return (\n \n {this.state.errors.map((err, index) => {\n if (!err) return;\n return (\n \n this.setState(prev => {\n const newarr = Array.from(prev.errors);\n newarr[index] = undefined;\n return {\n errors: newarr\n };\n })\n }\n />\n );\n })}\n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n {(this.context.serverInfo?.oAuthProviderInfos?.Discord ||\n this.context.serverInfo?.oAuthProviderInfos?.GitHub ||\n this.context.serverInfo?.oAuthProviderInfos?.Keycloak ||\n this.context.serverInfo?.oAuthProviderInfos?.TGForums ||\n this.context.serverInfo?.oAuthProviderInfos?.InvisionCommunity) && (\n <>\n
\n \n \n \n \n {Object.keys(this.context.serverInfo.oAuthProviderInfos ?? {}).map(\n provider => {\n const ptheme = providersTheme[provider as OAuthProvider];\n return (\n \n this.startOAuth(provider as OAuthProvider)\n }>\n {providers[provider as OAuthProvider]}\n \n \n \n \n );\n }\n )}\n \n \n )}\n
\n \n );\n }\n\n private async startOAuth(provider: OAuthProvider): Promise {\n if (!this.context.serverInfo) {\n this.addError(\n new InternalError(ErrorCode.APP_FAIL, {\n jsError: Error(\"serverInfo is null in startOAuth\")\n })\n );\n return;\n }\n\n const stateArray = new Uint8Array(10);\n window.crypto.getRandomValues(stateArray);\n const state = Array.from(stateArray, dec => dec.toString(16).padStart(2, \"0\")).join(\"\");\n\n let url: string | undefined = undefined;\n\n const e = encodeURIComponent;\n\n switch (provider) {\n case OAuthProvider.Discord: {\n url = `https://discord.com/api/oauth2/authorize?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.Discord.clientId\n )}&scope=identify&state=${e(state)}`;\n const discordRedirect = this.context.serverInfo.oAuthProviderInfos.Discord\n .redirectUri;\n if (discordRedirect) {\n url = `${url}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri\n )}`;\n }\n\n break;\n }\n case OAuthProvider.GitHub: {\n url = `https://github.com/login/oauth/authorize?client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.GitHub.clientId\n )}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.GitHub.redirectUri\n )}&state=${e(state)}&allow_signup=false`;\n break;\n }\n case OAuthProvider.Keycloak: {\n url = `${this.context.serverInfo.oAuthProviderInfos.Keycloak\n .serverUrl!}/protocol/openid-connect/auth?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.Keycloak.clientId\n )}&scope=openid&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.Keycloak.redirectUri\n )}`;\n break;\n }\n case OAuthProvider.TGForums: {\n url = `https://tgstation13.org/phpBB/app.php/tgapi/oauth/auth?scope=user&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.TGForums.clientId\n )}&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.TGForums.redirectUri\n )}`;\n break;\n }\n case OAuthProvider.InvisionCommunity: {\n url = `${this.context.serverInfo.oAuthProviderInfos.InvisionCommunity\n .serverUrl!}/oauth/authorize/?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.clientId\n )}&scope=profile&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.redirectUri\n )}`;\n break;\n }\n }\n\n const oauthdata = JSON.parse(\n window.sessionStorage.getItem(\"oauth\") ?? \"{}\"\n ) as OAuthStateStorage;\n oauthdata[state] = {\n provider: provider,\n url: this.props.location.pathname\n };\n\n window.sessionStorage.setItem(\"oauth\", JSON.stringify(oauthdata));\n\n window.location.href = url;\n\n return new Promise(resolve => resolve());\n }\n\n private async submit(event: FormEvent) {\n event.preventDefault();\n this.setState({\n busy: true\n });\n const response = await ServerClient.login({\n type: CredentialsType.Password,\n userName: this.state.username,\n password: this.state.password\n });\n if (response.code == StatusCode.ERROR) {\n this.setState({\n busy: false\n });\n this.addError(response.error);\n }\n }\n}\nLogin.contextType = GeneralContext;\nexport default withRouter(Login);\n","import React from \"react\";\n\nimport type { ServerInformationResponse, UserResponse } from \"../ApiClient/generatedcode/generated\";\nimport InternalError from \"../ApiClient/models/InternalComms/InternalError\";\n\nexport type GeneralContext = {\n deleteError: (error: InternalError) => void;\n errors: Set;\n user: UserResponse;\n serverInfo: ServerInformationResponse;\n};\n\n//same as GeneralContext except used for components which arent loading under the router so we cant guarentee that serverInfo and user wont be null\nexport type UnsafeGeneralContext = {\n deleteError: (error: InternalError) => void;\n errors: Set;\n user: UserResponse | null;\n serverInfo: ServerInformationResponse | null;\n};\n\nexport const GeneralContext = React.createContext(\n (undefined as unknown) as GeneralContext\n);\n","declare const API_VERSION: string;\ndeclare const VERSION: string;\ndeclare const MODE: \"DEV\" | \"PROD\" | \"GITHUB\";\ndeclare const DEFAULT_BASEPATH: string;\ndeclare const DEFAULT_APIPATH: string;\n\nconst _API_VERSION = API_VERSION;\nconst _VERSION = VERSION;\nconst _MODE = MODE;\nconst _DEFAULT_BASEPATH = DEFAULT_BASEPATH;\nconst _DEFAULT_APIPATH = DEFAULT_APIPATH;\n\nexport { _API_VERSION as API_VERSION };\nexport { _VERSION as VERSION };\nexport { _MODE as MODE };\nexport { _DEFAULT_BASEPATH as DEFAULT_BASEPATH };\nexport { _DEFAULT_APIPATH as DEFAULT_APIPATH };\n","declare let __webpack_public_path__: string;\ndeclare let MODE: \"GITHUB\" | \"PROD\" | \"DEV\";\n\n// eslint-disable-next-line prefer-const\nif (window.publicPath && MODE !== \"GITHUB\") __webpack_public_path__ = window.publicPath;\nconsole.log(\"Public path:\", __webpack_public_path__);\n\nexport {};\n","import api from \"!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../node_modules/css-loader/dist/cjs.js!./App.css\";\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import api from \"!../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../node_modules/css-loader/dist/cjs.js!./AppNavbar.css\";\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import \"./AppNavbar.css\";\n\nimport { faExclamationCircle } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React from \"react\";\nimport { NavDropdown, OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport Button from \"react-bootstrap/Button\";\nimport Dropdown from \"react-bootstrap/Dropdown\";\nimport Nav from \"react-bootstrap/Nav\";\nimport Navbar from \"react-bootstrap/Navbar\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\nimport { SemVer } from \"semver\";\n\nimport AdminClient from \"../ApiClient/AdminClient\";\nimport { AdministrationRights } from \"../ApiClient/generatedcode/generated\";\nimport { StatusCode } from \"../ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"../ApiClient/ServerClient\";\nimport UserClient from \"../ApiClient/UserClient\";\nimport LoginHooks from \"../ApiClient/util/LoginHooks\";\nimport { GeneralContext, UnsafeGeneralContext } from \"../contexts/GeneralContext\";\nimport { hasAdminRight, matchesPath, resolvePermissionSet } from \"../utils/misc\";\nimport RouteController from \"../utils/RouteController\";\nimport { AppCategories, AppRoute, AppRoutes } from \"../utils/routes\";\n\ninterface IProps extends RouteComponentProps {\n category?: {\n name: string;\n key: string;\n };\n loggedIn: boolean;\n}\n\ninterface IState {\n //so we dont actually use the routes but it allows us to make react update the component\n routes: AppRoute[];\n categories: typeof AppCategories;\n updateAvailable: boolean;\n}\n\nclass AppNavbar extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: IProps) {\n super(props);\n this.logoutClick = this.logoutClick.bind(this);\n this.loginSuccess = this.loginSuccess.bind(this);\n this.logout = this.logout.bind(this);\n this.refresh = this.refresh.bind(this);\n\n this.state = {\n routes: [],\n categories: AppCategories,\n updateAvailable: false\n };\n }\n\n private loginSuccess(): void {\n void this.checkShowServerUpdateIcon();\n }\n\n private async checkShowServerUpdateIcon(): Promise {\n await ServerClient.wait4Init();\n const userResponse = await UserClient.getCurrentUser();\n if (userResponse.code === StatusCode.ERROR) return;\n\n const user = userResponse.payload;\n\n const permissionSet = resolvePermissionSet(user);\n if (hasAdminRight(permissionSet, AdministrationRights.ChangeVersion)) {\n const response = await AdminClient.getAdminInfo();\n if (response.code == StatusCode.OK) {\n const latestVersion = new SemVer(response.payload.latestVersion);\n const currentVersion = new SemVer(this.context.serverInfo!.version);\n\n const updateAvailable = latestVersion.compare(currentVersion) === 1;\n\n this.setState({\n updateAvailable\n });\n }\n }\n }\n\n private logout() {\n this.setState({\n updateAvailable: false\n });\n }\n\n private refresh(routes: Array) {\n this.setState({\n routes\n });\n }\n\n public async componentDidMount(): Promise {\n LoginHooks.on(\"loginSuccess\", this.loginSuccess);\n ServerClient.on(\"logout\", this.logout);\n\n this.setState({\n routes: await RouteController.getRoutes()\n });\n\n RouteController.on(\"refresh\", this.refresh);\n }\n\n public componentWillUnmount(): void {\n LoginHooks.removeListener(\"loginSuccess\", this.loginSuccess);\n ServerClient.removeListener(\"logout\", this.logout);\n RouteController.removeListener(\"refresh\", this.refresh);\n }\n\n public render(): React.ReactNode {\n return (\n \n \n {\n this.props.history.push(AppRoutes.home.link ?? AppRoutes.home.route, {\n reload: true\n });\n }}\n className=\"mr-auto\">\n {this.renderVersion()}\n \n \n \n \n {this.state.updateAvailable ? (\n (\n \n \n \n )}>\n

\n \n this.props.history.push(\n AppRoutes.admin_update.link ??\n AppRoutes.admin_update.route,\n { reload: true }\n )\n }\n icon={faExclamationCircle}\n />\n

\n \n ) : (\n \n )}\n {this.renderUser()}\n
\n \n
\n );\n }\n\n private renderVersion(): React.ReactNode {\n if (!this.context.serverInfo?.version) {\n return ;\n }\n\n return (\n \n \n {\" v\"}\n {this.context.serverInfo.version}\n \n );\n }\n\n private renderUser(): React.ReactNode {\n if (!this.props.loggedIn)\n return (\n \n {\n this.props.history.push(\n AppRoutes.config.link ?? AppRoutes.config.route,\n { reload: true }\n );\n }}\n variant=\"primary\">\n \n \n {\n this.props.history.push(AppRoutes.info.link ?? AppRoutes.info.route, {\n reload: true\n });\n }}\n variant=\"primary\">\n \n \n \n );\n\n return (\n \n \n \n {this.context.user ? (\n this.context.user.name\n ) : (\n \n )}\n \n \n {\n this.props.history.push(\n AppRoutes.info.link ?? AppRoutes.info.route,\n { reload: true }\n );\n }}>\n \n \n {\n this.props.history.push(\n AppRoutes.config.link ?? AppRoutes.config.route,\n { reload: true }\n );\n }}>\n \n \n {AppRoutes.passwd.cachedAuth ? (\n {\n this.props.history.push(\n AppRoutes.passwd.link ?? AppRoutes.passwd.route,\n { reload: true }\n );\n }}>\n \n \n ) : (\n \"\"\n )}\n {\n ServerClient.emit(\"purgeCache\");\n this.props.history.replace(this.props.location.pathname, {\n reload: true\n });\n }}>\n \n \n {\n this.props.history.replace(this.props.location.pathname, {\n reload: true\n });\n }}>\n \n \n \n \n \n \n \n \n );\n }\n\n private logoutClick(): void {\n ServerClient.logout();\n }\n}\nAppNavbar.contextType = GeneralContext;\nexport default withRouter(AppNavbar);\n","import React from \"react\";\nimport { OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport logo from \"../images/logo.svg\";\n\ninterface IProps {}\n\ninterface IState {}\n\nexport default class Logo extends React.Component {\n public render(): React.ReactNode {\n let memeSelector = 4;\n return (\n {\n if (showing) {\n memeSelector = Math.round(Math.random() * 100) % 26;\n }\n }}\n overlay={props => (\n \n \n \n )}>\n \n \n );\n }\n}\n","import { faExclamationTriangle } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React from \"react\";\nimport { Button, OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport { FormattedMessage } from \"react-intl\";\n\ninterface IProps {}\n\ninterface IState {}\n\nexport default class ReportIssue extends React.Component {\n public render(): React.ReactNode {\n return (\n (\n \n \n \n )}>\n \n window.open(\n \"https://github.com/tgstation/tgstation-server-webpanel/issues/new\"\n )\n }>\n \n \n \n );\n }\n}\n","import React from \"react\";\nimport Card from \"react-bootstrap/Card\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\nimport { MODE, VERSION } from \"../../definitions/constants\";\n\ninterface IProps extends RouteComponentProps {}\ninterface IState {\n error?: Error;\n errorInfo?: React.ErrorInfo;\n}\n\nclass ErrorBoundary extends React.Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {};\n }\n\n public componentDidUpdate(prevProps: IProps): void {\n if (this.props.location.key !== prevProps.location.key) {\n this.setState({\n error: undefined,\n errorInfo: undefined\n });\n }\n }\n\n public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.setState({\n error,\n errorInfo\n });\n }\n\n public render(): React.ReactNode {\n if (this.state.error) {\n return (\n \n \n \n \n \n \n \n {this.state.error.name}: {this.state.error.message}\n \n \n \n {`Webpanel Version: ${VERSION}\\nWebpanel Mode: ${MODE}\\nStack trace: ${\n this.state.errorInfo?.componentStack ??\n \"Unable to get stack info\"\n }`}\n \n \n \n \n \n );\n } else {\n return this.props.children;\n }\n }\n}\n\nexport default withRouter(ErrorBoundary);\n","import { Component, ReactNode } from \"react\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\ninterface IProps\n extends RouteComponentProps<\n Record,\n {\n statusCode?: number;\n },\n { reload?: boolean }\n > {}\ninterface IState {\n clear: boolean;\n}\n\nclass Reload extends Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {\n clear: false\n };\n }\n public componentDidUpdate(prevProps: IProps): void {\n if (this.state.clear) {\n this.setState({\n clear: false\n });\n return;\n }\n if (\n prevProps.match.path == this.props.match.path &&\n prevProps.location.key != this.props.location.key &&\n this.props.location.state?.reload\n ) {\n this.setState({\n clear: true\n });\n }\n }\n\n public render(): ReactNode {\n return this.state.clear ? \"\" : this.props.children;\n }\n}\n\nexport default withRouter(Reload);\n","import loadable, { LoadableComponent } from \"@loadable/component\";\nimport * as React from \"react\";\nimport { Component, ComponentClass, ReactNode } from \"react\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\nimport { Route, Switch } from \"react-router-dom\";\n\nimport { CredentialsType } from \"./ApiClient/models/ICredentials\";\nimport InternalError, { ErrorCode } from \"./ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"./ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"./ApiClient/ServerClient\";\nimport AccessDenied from \"./components/utils/AccessDenied\";\nimport ErrorAlert from \"./components/utils/ErrorAlert\";\nimport ErrorBoundary from \"./components/utils/ErrorBoundary\";\nimport Loading from \"./components/utils/Loading\";\nimport Reload from \"./components/utils/Reload\";\nimport Login, { OAuthStateStorage } from \"./components/views/Login\";\nimport { GeneralContext, UnsafeGeneralContext } from \"./contexts/GeneralContext\";\nimport { MODE } from \"./definitions/constants\";\nimport { matchesPath } from \"./utils/misc\";\nimport RouteController from \"./utils/RouteController\";\nimport { AppRoute, AppRoutes, RouteData } from \"./utils/routes\";\n\ninterface IState {\n loading: boolean;\n routes: Array;\n components: Map>;\n}\ninterface IProps extends RouteComponentProps {\n loggedIn: boolean;\n loggedOut: boolean;\n selectCategory: (category: string) => void;\n}\n\nconst LoadSpin = (page: string) => (\n \n \n \n);\n\nconst NotFound = loadable(() => import(\"./components/utils/NotFound\"), {\n fallback: LoadSpin(\"loading.page.notfound\")\n});\n\nclass Router extends Component {\n public declare context: UnsafeGeneralContext;\n public constructor(props: IProps) {\n super(props);\n\n this.refreshListener = this.refreshListener.bind(this);\n\n const components = new Map>();\n\n const routes = RouteController.getImmediateRoutes(false);\n routes.forEach(route => {\n components.set(\n route.name,\n //*should* always be a react component\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n loadable(() => import(`./components/views/${route.file}`), {\n fallback: LoadSpin(route.name)\n })\n );\n });\n\n this.state = {\n loading: !!new URLSearchParams(window.location.search).get(\"state\"),\n routes: RouteController.getImmediateRoutes(false),\n components: components\n };\n }\n\n private refreshListener(routes: Array) {\n this.setState({\n routes\n });\n }\n\n public async componentDidMount() {\n RouteController.on(\"refreshAll\", this.refreshListener);\n\n this.props.history.listen(location => {\n void this.listener(location.pathname);\n });\n this.listener(this.props.location.pathname);\n\n const URLSearch = new URLSearchParams(window.location.search);\n const state = URLSearch.get(\"state\");\n if (!state) {\n this.setState({\n loading: false\n });\n return;\n }\n\n if (MODE === \"PROD\" || MODE === \"GITHUB\") {\n window.history.replaceState(null, document.title, window.location.pathname);\n }\n\n const oauthdata = JSON.parse(\n window.sessionStorage.getItem(\"oauth\") ?? \"{}\"\n ) as OAuthStateStorage;\n\n const oauthstate = oauthdata[state];\n if (!oauthstate) {\n return this.setErrorAndEnd(\n new InternalError(ErrorCode.LOGIN_BAD_OAUTH, {\n jsError: Error(`State(${state}) cannot be resolved to a provider.`)\n })\n );\n }\n\n const code = URLSearch.get(\"code\");\n if (!code) {\n return this.setErrorAndEnd(\n new InternalError(ErrorCode.LOGIN_BAD_OAUTH, {\n jsError: Error(`Code not found.`)\n })\n );\n }\n this.props.history.replace(oauthstate.url);\n\n const response = await ServerClient.login({\n type: CredentialsType.OAuth,\n provider: oauthstate.provider,\n token: code\n });\n\n window.sessionStorage.removeItem(\"oauth\");\n\n if (response.code === StatusCode.OK) {\n this.setState({\n loading: false\n });\n } else {\n return this.setErrorAndEnd(response.error);\n }\n }\n\n public componentWillUnmount(): void {\n RouteController.removeListener(\"refreshAll\", this.refreshListener);\n }\n\n private setErrorAndEnd(error: InternalError) {\n RouteData.oautherrors = [error];\n this.setState({\n loading: false\n });\n }\n\n private listener(location: string) {\n const routes = RouteController.getImmediateRoutes(false);\n for (const route of routes) {\n if (route.category && route.navbarLoose && matchesPath(location, route.route)) {\n this.props.selectCategory(route.category);\n break;\n }\n }\n }\n\n public render(): ReactNode {\n if (this.state.loading) {\n return ;\n }\n\n return (\n \n \n
\n \n {this.state.routes.map(route => {\n if (!route.loginless && !this.props.loggedIn) return;\n\n return (\n {\n let Comp;\n\n if (!route.cachedAuth) {\n Comp = AccessDenied;\n } else {\n Comp = this.state.components.get(\n route.name\n )! as ComponentClass;\n }\n\n return !this.context?.user && !route.loginless ? (\n \n \n \n ) : //Yeah I have no excuse for this, I didn't want to implement a route config option\n // to allow a single route to work without server info so i added it as a check here\n !this.context?.serverInfo &&\n route != AppRoutes.config ? (\n \n \n \n ) : route.noContainer ? (\n \n \n \n ) : (\n \n \n \n );\n }}\n />\n );\n })}\n \n \n {this.props.loggedIn ? (\n \n ) : (\n \n )}\n \n \n \n
\n
\n
\n );\n }\n}\nRouter.contextType = GeneralContext;\nexport default withRouter(Router);\n","class Locales {\n public static readonly en: string = \"en\";\n}\n\nexport default Locales;\n","import ILocalization from \"./ILocalization\";\nimport ITranslation from \"./ITranslation\";\n\nexport default class Translation implements ITranslation {\n public constructor(public readonly locale: string, public readonly messages: ILocalization) {}\n}\n","import ITranslation from \"./ITranslation\";\nimport ITranslationFactory from \"./ITranslationFactory\";\nimport Locales from \"./Locales\";\nimport Translation from \"./Translation\";\n\nclass TranslationFactory implements ITranslationFactory {\n private static readonly fallbackLocale: string = Locales.en;\n\n private static getShortHandedLocale(locale: string): string {\n return locale.split(\"-\")[0];\n }\n\n public async loadTranslation(locale: string): Promise {\n //fancy type annotations but its just load the json file in this variable as a map of strings to strings\n const localization: { [key: string]: string } = (await import(\n `./locales/${locale}.json`\n )) as { [key: string]: string };\n\n if (!localization) {\n let shortHandedLocale = TranslationFactory.getShortHandedLocale(locale);\n if (shortHandedLocale === locale) {\n if (shortHandedLocale === TranslationFactory.fallbackLocale)\n throw new Error(\"Invalid locale: \" + locale);\n shortHandedLocale = TranslationFactory.fallbackLocale;\n }\n return await this.loadTranslation(shortHandedLocale);\n }\n\n let model: ITranslation | null = null;\n try {\n model = new Translation(locale, localization);\n } catch (e) {\n throw Error(`Error loading localization for locale '${locale}': ${JSON.stringify(e)}`);\n }\n\n return model;\n }\n}\n\nexport default TranslationFactory;\n","import \"./App.css\";\n\nimport * as React from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage, IntlProvider } from \"react-intl\";\nimport { BrowserRouter } from \"react-router-dom\";\n\nimport Pkg from \"./../package.json\";\nimport InternalError, {\n ErrorCode,\n GenericErrors\n} from \"./ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"./ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"./ApiClient/ServerClient\";\nimport UserClient from \"./ApiClient/UserClient\";\nimport CredentialsProvider from \"./ApiClient/util/CredentialsProvider\";\nimport LoginHooks from \"./ApiClient/util/LoginHooks\";\nimport AppNavbar from \"./components/AppNavbar\";\nimport Logo from \"./components/Logo\";\nimport ReportIssue from \"./components/ReportIssue\";\nimport ErrorAlert from \"./components/utils/ErrorAlert\";\nimport ErrorBoundary from \"./components/utils/ErrorBoundary\";\nimport JobsList from \"./components/utils/JobsList\";\nimport Loading from \"./components/utils/Loading\";\nimport { GeneralContext, UnsafeGeneralContext } from \"./contexts/GeneralContext\";\nimport { DEFAULT_BASEPATH } from \"./definitions/constants\";\nimport Router from \"./Router\";\nimport ITranslation from \"./translations/ITranslation\";\nimport ITranslationFactory from \"./translations/ITranslationFactory\";\nimport Locales from \"./translations/Locales\";\nimport TranslationFactory from \"./translations/TranslationFactory\";\n\ninterface IState {\n translation?: ITranslation;\n translationError?: string;\n loggedIn: boolean;\n loggedOut: boolean;\n loading: boolean;\n GeneralContextInfo: UnsafeGeneralContext;\n}\n\ninterface IProps {\n readonly locale: string;\n readonly translationFactory?: ITranslationFactory;\n}\n\ninterface InnerProps {\n loading: boolean;\n loggedIn: boolean;\n loggedOut: boolean;\n}\n\ninterface InnerState {\n passdownCat?: { name: string; key: string };\n}\n\nclass InnerApp extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: InnerProps) {\n super(props);\n\n this.state = {};\n }\n\n public componentDidMount() {\n document.title = \"TGS Webpanel v\" + Pkg.version;\n // I can't be assed to remember the default admin password\n document.addEventListener(\"keydown\", event => {\n if (event.key === \"L\" && event.ctrlKey && event.shiftKey) {\n ServerClient.logout();\n void ServerClient.login(CredentialsProvider.default);\n }\n });\n }\n\n public render(): React.ReactNode {\n return (\n \n \n \n {this.props.loading ? (\n \n \n \n ) : (\n <>\n \n \n \n \n \n
\n \n
\n {Array.from(this.context.errors.values()).map((value, idx) => {\n return (\n this.context.deleteError(value)}\n />\n );\n })}\n
\n {\n this.setState({\n passdownCat: {\n name: cat,\n key: Math.random().toString()\n }\n });\n }}\n />\n \n )}\n {this.props.loggedIn ? : null}\n
\n \n \n \n );\n }\n}\nInnerApp.contextType = GeneralContext;\n\nclass App extends React.Component {\n private readonly translationFactory: ITranslationFactory;\n\n public constructor(props: IProps) {\n super(props);\n\n this.finishLogin = this.finishLogin.bind(this);\n this.finishLogout = this.finishLogout.bind(this);\n this.updateContextUser = this.updateContextUser.bind(this);\n this.updateContextServer = this.updateContextServer.bind(this);\n this.deleteGeneralContextError = this.deleteGeneralContextError.bind(this);\n\n this.translationFactory = this.props.translationFactory ?? new TranslationFactory();\n\n this.state = {\n loggedIn: false,\n loggedOut: false,\n loading: true,\n GeneralContextInfo: {\n errors: new Set(),\n user: null,\n serverInfo: null,\n deleteError: this.deleteGeneralContextError\n }\n };\n }\n\n private async updateContextUser() {\n const response = await UserClient.getCurrentUser();\n if (response.code === StatusCode.OK) {\n this.setState(prev => {\n return {\n GeneralContextInfo: {\n errors: prev.GeneralContextInfo.errors,\n user: response.payload,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n deleteError: prev.GeneralContextInfo.deleteError\n }\n };\n });\n } else {\n if (response.error.code === ErrorCode.HTTP_ACCESS_DENIED) {\n this.setState(prev => {\n return {\n GeneralContextInfo: {\n user: null,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n deleteError: prev.GeneralContextInfo.deleteError,\n errors: prev.GeneralContextInfo.errors\n }\n };\n });\n } else {\n setTimeout(() => void this.updateContextUser(), 5000);\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.add(response.error);\n return {\n GeneralContextInfo: {\n errors: newSet,\n deleteError: prev.GeneralContextInfo.deleteError,\n user: null,\n serverInfo: prev.GeneralContextInfo.serverInfo\n }\n };\n });\n }\n }\n }\n\n private async updateContextServer(lastError?: InternalError) {\n const response = await ServerClient.getServerInfo();\n if (response.code === StatusCode.OK) {\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n if (lastError) {\n newSet.delete(lastError);\n }\n return {\n GeneralContextInfo: {\n errors: newSet,\n user: prev.GeneralContextInfo.user,\n serverInfo: response.payload,\n deleteError: prev.GeneralContextInfo.deleteError\n }\n };\n });\n } else {\n setTimeout(() => void this.updateContextServer(response.error), 5000);\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.add(response.error);\n if (lastError) {\n newSet.delete(lastError);\n }\n return {\n GeneralContextInfo: {\n errors: newSet,\n deleteError: prev.GeneralContextInfo.deleteError,\n user: prev.GeneralContextInfo.user,\n serverInfo: null\n }\n };\n });\n }\n }\n\n public deleteGeneralContextError(error: InternalError): void {\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.delete(error);\n return {\n GeneralContextInfo: {\n deleteError: prev.GeneralContextInfo.deleteError,\n user: prev.GeneralContextInfo.user,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n errors: newSet\n }\n };\n });\n }\n\n private finishLogin() {\n console.log(\"Logging in\");\n\n void this.updateContextUser().then(() =>\n this.setState({\n loggedIn: true,\n loading: false\n })\n );\n }\n\n private finishLogout() {\n this.setState({\n loggedIn: false,\n loggedOut: true\n });\n\n void this.updateContextUser();\n }\n public async componentDidMount(): Promise {\n LoginHooks.on(\"loginSuccess\", this.finishLogin);\n ServerClient.on(\"logout\", this.finishLogout);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.on(\"purgeCache\", this.updateContextServer);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.on(\"purgeCache\", this.updateContextUser);\n\n await this.loadTranslation();\n const loggedInSuccessfully = await ServerClient.initApi();\n await this.updateContextServer();\n if (loggedInSuccessfully) {\n await this.updateContextUser();\n }\n\n this.setState({\n loading: false,\n loggedIn: loggedInSuccessfully\n });\n }\n\n public componentWillUnmount(): void {\n LoginHooks.removeListener(\"loginSuccess\", this.finishLogin);\n ServerClient.removeListener(\"logout\", this.finishLogout);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.removeListener(\"purgeCache\", this.updateContextServer);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.removeListener(\"purgeCache\", this.updateContextUser);\n }\n\n public render(): React.ReactNode {\n if (this.state.translationError) {\n return

{this.state.translationError}

;\n }\n\n if (!this.state.translation) {\n return Loading translations...;\n }\n return (\n \n \n \n \n \n );\n }\n\n private async loadTranslation(): Promise {\n console.time(\"LoadTranslations\");\n try {\n const translation = await this.translationFactory.loadTranslation(this.props.locale);\n this.setState({\n translation\n });\n } catch (error) {\n this.setState({\n translationError: JSON.stringify(error) ?? \"An unknown error occurred\"\n });\n\n return;\n }\n\n console.timeEnd(\"LoadTranslations\");\n }\n}\n\nexport default App;\n\nexport const IndexApp = (\n \n \n \n);\n","import { library } from \"@fortawesome/fontawesome-svg-core\";\nimport { faDiscord, faGithub } from \"@fortawesome/free-brands-svg-icons\";\nimport { faGitAlt } from \"@fortawesome/free-brands-svg-icons/faGitAlt\";\nimport {\n faArrowLeft,\n faCaretDown,\n faCaretRight,\n faClipboard,\n faComment,\n faExclamationTriangle,\n faFile,\n faFileAlt,\n faFolderMinus,\n faFolderPlus,\n faGamepad,\n faHashtag,\n faMinus,\n faUnlock,\n faUpload\n} from \"@fortawesome/free-solid-svg-icons\";\nimport { faAngleRight } from \"@fortawesome/free-solid-svg-icons/faAngleRight\";\nimport { faCheck } from \"@fortawesome/free-solid-svg-icons/faCheck\";\nimport { faCodeBranch } from \"@fortawesome/free-solid-svg-icons/faCodeBranch\";\nimport { faCogs } from \"@fortawesome/free-solid-svg-icons/faCogs\";\nimport { faComments } from \"@fortawesome/free-solid-svg-icons/faComments\";\nimport { faExclamationCircle } from \"@fortawesome/free-solid-svg-icons/faExclamationCircle\";\nimport { faFolderOpen } from \"@fortawesome/free-solid-svg-icons/faFolderOpen\";\nimport { faGripLinesVertical } from \"@fortawesome/free-solid-svg-icons/faGripLinesVertical\";\nimport { faHammer } from \"@fortawesome/free-solid-svg-icons/faHammer\";\nimport { faHdd } from \"@fortawesome/free-solid-svg-icons/faHdd\";\nimport { faHome } from \"@fortawesome/free-solid-svg-icons/faHome\";\nimport { faInfo } from \"@fortawesome/free-solid-svg-icons/faInfo\";\nimport { faInfoCircle } from \"@fortawesome/free-solid-svg-icons/faInfoCircle\";\nimport { faKey } from \"@fortawesome/free-solid-svg-icons/faKey\";\nimport { faListUl } from \"@fortawesome/free-solid-svg-icons/faListUl\";\nimport { faLock } from \"@fortawesome/free-solid-svg-icons/faLock\";\nimport { faPen } from \"@fortawesome/free-solid-svg-icons/faPen\";\nimport { faPlus } from \"@fortawesome/free-solid-svg-icons/faPlus\";\nimport { faQuestion } from \"@fortawesome/free-solid-svg-icons/faQuestion\";\nimport { faSearch } from \"@fortawesome/free-solid-svg-icons/faSearch\";\nimport { faServer } from \"@fortawesome/free-solid-svg-icons/faServer\";\nimport { faStream } from \"@fortawesome/free-solid-svg-icons/faStream\";\nimport { faSync } from \"@fortawesome/free-solid-svg-icons/faSync\";\nimport { faTimes } from \"@fortawesome/free-solid-svg-icons/faTimes\";\nimport { faTools } from \"@fortawesome/free-solid-svg-icons/faTools\";\nimport { faTrash } from \"@fortawesome/free-solid-svg-icons/faTrash\";\nimport { faUndo } from \"@fortawesome/free-solid-svg-icons/faUndo\";\nimport { faUser } from \"@fortawesome/free-solid-svg-icons/faUser\";\nimport { faUsers } from \"@fortawesome/free-solid-svg-icons/faUsers\";\nimport { faUserSlash } from \"@fortawesome/free-solid-svg-icons/faUserSlash\";\n\nexport default function (): void {\n library.add(\n faCheck,\n faTimes,\n faExclamationCircle,\n faUser,\n faUserSlash,\n faHdd,\n faSync,\n faPlus,\n faQuestion,\n faHome,\n faTools,\n faCogs,\n faUndo,\n faInfo,\n faGripLinesVertical,\n faAngleRight,\n faKey,\n faPen,\n faGithub,\n faDiscord,\n faTrash,\n faInfoCircle,\n faGitAlt,\n faHammer,\n faListUl,\n faComments,\n faFolderOpen,\n faUsers,\n faCodeBranch,\n faSearch,\n faServer,\n faStream,\n faLock,\n faMinus,\n faUnlock,\n faLock,\n faCaretRight,\n faCaretDown,\n faComment,\n faHashtag,\n faFolderPlus,\n faFolderMinus,\n faFile,\n faFileAlt,\n faExclamationTriangle,\n faClipboard,\n faArrowLeft,\n faAngleRight,\n faUpload,\n faGamepad\n );\n}\n","// eslint-disable-next-line\nimport \"./publicPath\";\n\n// definition files\n// css\nimport \"./styles/dark.scss\";\n// polyfills\nimport \"@formatjs/intl-relativetimeformat/polyfill\";\nimport \"@formatjs/intl-relativetimeformat/locale-data/en\";\nimport \"@formatjs/intl-pluralrules/polyfill\";\nimport \"@formatjs/intl-pluralrules/locale-data/en\";\n\nimport ReactDOM from \"react-dom\";\n\nimport ConfigController from \"./ApiClient/util/ConfigController\";\nimport JobsController from \"./ApiClient/util/JobsController\";\nimport { IndexApp } from \"./App\";\nimport { MODE, VERSION } from \"./definitions/constants\";\nimport initIcons from \"./utils/icolibrary\";\n\n// dont lag the dom\ninitIcons();\nConfigController.loadconfig();\nJobsController.init();\n\nif (window.loadedChannelFromWebpack && MODE !== \"DEV\") {\n alert(\n \"Warning: channel.json was served from bundled files instead of TGS, the webpanel is running from the local version instead of the github update repo.\\nPlease report this to your server host.\\nIf you are the server host, please report this to alexkar598#2712 on discord\\n\\nWebpanel version: \" +\n VERSION\n );\n}\n\n// At some point, the webpanel had the ability to save passwords, this is however,\n// insecure as compromised webhosts can lead to code being served from an untrusted source,\n// leaking the saved password. Makes sure it's not there anymore\ntry {\n window.localStorage.removeItem(\"username\");\n window.sessionStorage.removeItem(\"username\");\n window.localStorage.removeItem(\"password\");\n window.sessionStorage.removeItem(\"password\");\n} catch {\n (() => {})();\n}\n\nfunction mountApp() {\n ReactDOM.render(IndexApp, document.getElementById(\"root\"));\n}\n\nwindow.addEventListener(\"DOMContentLoaded\", mountApp);\nif (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n mountApp();\n}\n","import { TypedEmitter } from \"tiny-typed-emitter/lib\";\n\nimport LoginHooks from \"../ApiClient/util/LoginHooks\";\nimport {\n AppCategories,\n AppRoute,\n AppRoutes,\n UnpopulatedAppCategories,\n UnpopulatedAppCategory\n} from \"./routes\";\n\ninterface IEvents {\n refresh: (routes: Array) => void; //auth\n refreshAll: (routes: Array) => void; //noauth+auth\n}\n\n//helper class to process AppRoutes\nclass RouteController extends TypedEmitter {\n private refreshing = false;\n\n public constructor() {\n super();\n window.rtcontroller = this;\n this.refreshRoutes = this.refreshRoutes.bind(this);\n\n LoginHooks.addHook(this.refreshRoutes);\n this.refreshRoutes().catch(console.error);\n\n //process categories\n console.time(\"Category mapping\");\n const catmap = new Map();\n\n for (const [name, val] of Object.entries(UnpopulatedAppCategories)) {\n val.routes = [];\n //null asserted the name because that one is everywhere, even if the rest is partial\n catmap.set(val.name!, val);\n //@ts-expect-error typescript cannot infer that the name is a key of UnpopulatedAppCategories\n AppCategories[name] = val;\n }\n\n for (const route of Object.values(AppRoutes)) {\n if (!route.category) continue;\n\n const cat = catmap.get(route.category);\n if (!cat) {\n console.error(\"Route has invalid category\", route);\n continue;\n }\n\n //this is guaranteed to be an array as its set in the loop above\n cat.routes!.push(route);\n\n if (route.catleader) {\n if (cat.leader) {\n console.error(\"Category has two leaders\", cat.leader, route);\n continue;\n }\n cat.leader = route;\n }\n }\n console.log(\"Categories mapped\", catmap);\n console.timeEnd(\"Category mapping\");\n }\n\n public async refreshRoutes() {\n if (this.refreshing) {\n console.log(\"Already refreshing\");\n return;\n } //no need to refresh twice\n\n this.refreshing = true;\n\n const work = []; // we get all hidden routes no matter the authentification without waiting for the refresh\n const routes = this.getImmediateRoutes(false);\n\n for (const route of routes) {\n route.cachedAuth = undefined;\n if (route.isAuthorized) {\n work.push(\n route.isAuthorized().then(auth => {\n route.cachedAuth = auth;\n })\n );\n } else {\n route.cachedAuth = true;\n }\n }\n\n await Promise.all(work); //wait for all the authorized calls to complete\n\n this.emit(\"refresh\", this.getImmediateRoutes(true));\n const routesNoAuth = this.getImmediateRoutes(false);\n this.emit(\"refreshAll\", routesNoAuth);\n this.refreshing = false;\n\n console.log(\"Routes refreshed\", routesNoAuth);\n return await this.getRoutes();\n }\n\n private wait4refresh() {\n return new Promise(resolve => {\n if (!this.refreshing) {\n resolve();\n return;\n }\n this.on(\"refresh\", () => {\n resolve();\n });\n });\n }\n\n public async getRoutes(auth = true): Promise {\n await this.wait4refresh();\n\n return this.getImmediateRoutes(auth);\n }\n\n public getImmediateRoutes(auth = true) {\n const results: Array = [];\n\n for (const val of Object.values(AppRoutes)) {\n //we check for isauthorized here without calling because routes that lack the function are public\n if (val.isAuthorized && !val.cachedAuth && auth) continue; //if not authorized and we only show authorized routes\n\n results.push(val);\n }\n\n return results;\n }\n}\n\nexport default new RouteController();\n","import { pathToRegexp } from \"path-to-regexp\";\n\nimport {\n AdministrationRights,\n ChatBotRights,\n ConfigurationRights,\n DreamDaemonRights,\n DreamMakerRights,\n EngineRights,\n InstanceManagerRights,\n InstancePermissionSetResponse,\n InstancePermissionSetRights,\n PermissionSet,\n RepositoryRights,\n UserResponse\n} from \"../ApiClient/generatedcode/generated\";\n\nexport type DistributiveOmit = T extends T ? Omit : never;\n\nfunction download(filename: string, text: string): void {\n const element = document.createElement(\"a\");\n element.setAttribute(\"href\", \"data:text/plain;charset=utf-8,\" + encodeURIComponent(text));\n element.setAttribute(\"download\", filename);\n\n element.style.display = \"none\";\n document.body.appendChild(element);\n\n element.click();\n\n document.body.removeChild(element);\n}\n\nfunction replaceAll(str: string, find: string, replace: string, ignore?: boolean): string {\n return str.replace(\n new RegExp(find.replace(/([/,!\\\\^${}[\\]().*+?|<>\\-&])/g, \"\\\\$&\"), ignore ? \"gi\" : \"g\"),\n replace.replace(/\\$/g, \"$$$$\")\n );\n}\n\nfunction matchesPath(path: string, target: string, exact = false): boolean {\n //remove trailing slashes\n if (path.slice(-1) === \"/\") path = path.slice(0, -1);\n if (target.slice(-1) === \"/\") target = target.slice(0, -1);\n\n return pathToRegexp(target, undefined, { end: exact }).test(path);\n}\n\nfunction resolvePermissionSet(user: UserResponse): PermissionSet {\n return (user.permissionSet ?? user.group?.permissionSet) as PermissionSet;\n}\n\nfunction bitflagIsTrue(bitfield: number, bitflag: number): boolean {\n return !!(bitflag & bitfield);\n}\n\nfunction hasAdminRight(permissionSet: PermissionSet, right: AdministrationRights): boolean {\n return bitflagIsTrue(permissionSet.administrationRights, right);\n}\n\nfunction hasInstanceManagerRight(\n permissionSet: PermissionSet,\n right: InstanceManagerRights\n): boolean {\n return bitflagIsTrue(permissionSet.instanceManagerRights, right);\n}\n\nfunction hasEngineRight(\n permissionSet: InstancePermissionSetResponse,\n right: EngineRights\n): boolean {\n return bitflagIsTrue(permissionSet.engineRights, right);\n}\n\nfunction hasChatBotRight(\n permissionSet: InstancePermissionSetResponse,\n right: ChatBotRights\n): boolean {\n return bitflagIsTrue(permissionSet.chatBotRights, right);\n}\n\nfunction hasConfigRight(\n permissionSet: InstancePermissionSetResponse,\n right: ConfigurationRights\n): boolean {\n return bitflagIsTrue(permissionSet.configurationRights, right);\n}\n\nfunction hasDreamDaemonRight(\n permissionSet: InstancePermissionSetResponse,\n right: DreamDaemonRights\n): boolean {\n return bitflagIsTrue(permissionSet.dreamDaemonRights, right);\n}\n\nfunction hasDreamMakerRight(\n permissionSet: InstancePermissionSetResponse,\n right: DreamMakerRights\n): boolean {\n return bitflagIsTrue(permissionSet.dreamMakerRights, right);\n}\n\nfunction hasInstancePermRight(\n permissionSet: InstancePermissionSetResponse,\n right: InstancePermissionSetRights\n): boolean {\n return bitflagIsTrue(permissionSet.instancePermissionSetRights, right);\n}\n\nfunction hasRepoRight(\n permissionSet: InstancePermissionSetResponse,\n right: RepositoryRights\n): boolean {\n return bitflagIsTrue(permissionSet.repositoryRights, right);\n}\n\nfunction hasFilesRight(\n permissionSet: InstancePermissionSetResponse,\n right: ConfigurationRights\n): boolean {\n return bitflagIsTrue(permissionSet.configurationRights, right);\n}\n\nexport {\n download,\n replaceAll,\n matchesPath,\n resolvePermissionSet,\n bitflagIsTrue,\n hasAdminRight,\n hasEngineRight,\n hasConfigRight,\n hasRepoRight,\n hasChatBotRight,\n hasInstancePermRight,\n hasInstanceManagerRight,\n hasDreamMakerRight,\n hasDreamDaemonRight,\n hasFilesRight\n};\n","import { IconProp } from \"@fortawesome/fontawesome-svg-core\";\n\nimport { AdministrationRights, InstanceManagerRights } from \"../ApiClient/generatedcode/generated\";\nimport InternalError, { ErrorCode } from \"../ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"../ApiClient/models/InternalComms/InternalStatus\";\nimport UserClient from \"../ApiClient/UserClient\";\nimport CredentialsProvider from \"../ApiClient/util/CredentialsProvider\";\nimport { resolvePermissionSet } from \"./misc\";\n\nexport interface AppRoute {\n ///Base parameters\n //must be unique, also is the id of the route name message\n name: string;\n //must be unique, url to access\n route: string;\n //link to link to when linking to the route, defaults to the \"route\"\n link?: string;\n //filename in components/view that the route should display\n file: string;\n\n ///Path parameters\n //If subpaths should route here\n loose: boolean;\n //If subpaths should light up the navbar button\n navbarLoose: boolean;\n\n ///Authentication\n //if we can route to it even on the login page\n loginless?: boolean;\n //function to tell if we are authorized\n isAuthorized?: () => Promise;\n //result of isAuthorized() after RouteController runs it, can be used by components but only set by RouteController\n cachedAuth?: boolean;\n\n ///Visibility\n //if this shows up on the navbar\n visibleNavbar: boolean;\n //serves two purposes, first one is to give it an icon, the second one is to not display it if the icon is undefined\n homeIcon?: IconProp;\n\n ///Categories\n //name of the category it belongs to\n category?: string;\n //if this is the main button in the category\n catleader?: boolean;\n\n ///Misc\n //Should we not wrap this component in a ?\n noContainer?: boolean;\n}\n\nfunction adminRight(right: AdministrationRights) {\n return async (): Promise => {\n if (!CredentialsProvider.hasToken()) return false;\n const response = await UserClient.getCurrentUser();\n\n if (response.code == StatusCode.OK) {\n return !!(resolvePermissionSet(response.payload).administrationRights & right);\n }\n return false;\n };\n}\n\nfunction instanceManagerRight(right: InstanceManagerRights) {\n return async (): Promise => {\n if (!CredentialsProvider.hasToken()) return false;\n const response = await UserClient.getCurrentUser();\n\n if (response.code == StatusCode.OK) {\n return !!(resolvePermissionSet(response.payload).instanceManagerRights & right);\n }\n return false;\n };\n}\n\n//https://stackoverflow.com/questions/54598322/how-to-make-typescript-infer-the-keys-of-an-object-but-define-type-of-its-value\n//Infer the keys but restrict the values to a type\nconst asElementTypesAppRoute = (et: { [K in keyof T]: AppRoute }) => et;\n\nconst AppRoutes = asElementTypesAppRoute({\n home: {\n name: \"routes.home\",\n route: \"/\",\n file: \"Home\",\n\n loose: false,\n navbarLoose: false,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"home\",\n catleader: true\n },\n instancecreate: {\n name: \"routes.instancecreate\",\n route: \"/instances/create\",\n file: \"Instance/Create\",\n\n loose: false,\n navbarLoose: false,\n\n isAuthorized: instanceManagerRight(InstanceManagerRights.Create),\n\n visibleNavbar: false,\n\n category: \"instance\",\n catleader: false\n },\n instancelist: {\n name: \"routes.instancelist\",\n route: \"/instances/\",\n file: \"Instance/List\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: instanceManagerRight(InstanceManagerRights.List | InstanceManagerRights.Read),\n\n visibleNavbar: true,\n homeIcon: \"hdd\",\n\n category: \"instance\",\n catleader: true\n },\n instanceedit: {\n name: \"routes.instanceedit\",\n route: \"/instances/edit/:id(\\\\d+)/:tab?/\",\n file: \"Instance/InstanceEdit\",\n\n get link(): string {\n return RouteData.selectedinstanceid !== undefined\n ? `/instances/edit/${RouteData.selectedinstanceid}/${\n RouteData.selectedinstanceedittab !== undefined\n ? `${RouteData.selectedinstanceedittab}/`\n : \"\"\n }`\n : AppRoutes.instancelist.link ?? AppRoutes.instancelist.route;\n },\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"instance\"\n },\n instancejobs: {\n name: \"routes.instancejobs\",\n route: \"/instances/jobs/\",\n file: \"Instance/Jobs\",\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"instance\"\n },\n userlist: {\n name: \"routes.usermanager\",\n route: \"/users/\",\n file: \"User/List\",\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: \"user\",\n\n category: \"user\",\n catleader: true\n },\n useredit: {\n name: \"routes.useredit\",\n route: \"/users/edit/user/:id(\\\\d+)/:tab?/\",\n\n //whole lot of bullshit just to make it that if you have an id, link to the edit page, otherwise link to the list page, and if you link to the user page, put the tab in\n get link(): string {\n return RouteData.selecteduserid !== undefined\n ? `/users/edit/user/${RouteData.selecteduserid}/${\n RouteData.selectedusertab !== undefined ? `${RouteData.selectedusertab}/` : \"\"\n }`\n : AppRoutes.userlist.link ?? AppRoutes.userlist.route;\n },\n file: \"User/Edit\",\n\n loose: true,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"user\"\n },\n usercreate: {\n name: \"routes.usercreate\",\n route: \"/users/create/\",\n\n link: \"/users/create/\",\n file: \"User/Create\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.WriteUsers),\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"user\"\n },\n admin: {\n name: \"routes.admin\",\n route: \"/admin/\",\n file: \"Administration\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: adminRight(\n AdministrationRights.ChangeVersion |\n AdministrationRights.DownloadLogs |\n AdministrationRights.UploadVersion\n ),\n\n visibleNavbar: true,\n homeIcon: \"tools\",\n\n category: \"admin\",\n catleader: true\n },\n admin_update: {\n name: \"routes.admin.update\",\n route: \"/admin/update/:all?/\",\n file: \"Admin/Update\",\n\n link: \"/admin/update/\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(\n AdministrationRights.ChangeVersion | AdministrationRights.UploadVersion\n ),\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"admin\"\n },\n admin_logs: {\n name: \"routes.admin.logs\",\n route: \"/admin/logs/:name?/\",\n link: \"/admin/logs/\",\n file: \"Admin/Logs\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.DownloadLogs),\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"admin\",\n\n noContainer: true\n },\n passwd: {\n name: \"routes.passwd\",\n route: \"/users/passwd/:id(\\\\d+)?/\",\n link: \"/users/passwd/\",\n file: \"ChangePassword\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.EditOwnPassword),\n\n visibleNavbar: false,\n homeIcon: \"key\"\n },\n config: {\n name: \"routes.config\",\n route: \"/config/\",\n file: \"Configuration\",\n\n loose: true,\n navbarLoose: true,\n\n loginless: true,\n\n visibleNavbar: false,\n homeIcon: \"cogs\"\n },\n setup: {\n name: \"routes.setup\",\n route: \"/setup/\",\n file: \"Setup\",\n\n loose: true,\n navbarLoose: true,\n\n loginless: true,\n\n visibleNavbar: false\n },\n oAuth: {\n name: \"routes.oauth\",\n route: \"/oauth/:provider?/\",\n file: \"Login\",\n\n loose: true,\n navbarLoose: false,\n\n loginless: true,\n\n visibleNavbar: false\n },\n info: {\n name: \"routes.info\",\n route: \"/info\",\n file: \"Info\",\n\n loose: false,\n navbarLoose: false,\n\n loginless: true,\n\n visibleNavbar: true,\n homeIcon: \"info-circle\",\n\n category: undefined,\n catleader: false\n }\n});\n\nexport { AppRoutes };\n\n//https://stackoverflow.com/questions/54598322/how-to-make-typescript-infer-the-keys-of-an-object-but-define-type-of-its-value\n//Infer the keys but restrict the values to a type\nconst asElementTypesCategory = (et: { [K in keyof T]: UnpopulatedAppCategory }) => et;\n\nexport type UnpopulatedAppCategory = Partial;\n\nexport interface AppCategory {\n name: string; //doesnt really matter, kinda bullshit\n routes: AppRoute[];\n leader: AppRoute;\n}\n\nexport const UnpopulatedAppCategories = asElementTypesCategory({\n home: {\n name: \"home\"\n },\n instance: {\n name: \"instance\"\n },\n user: {\n name: \"user\"\n },\n admin: {\n name: \"admin\"\n }\n});\n\n// @ts-expect-error This is populated in the constructor after its populated\nexport const AppCategories: { [K in keyof typeof UnpopulatedAppCategories]: AppCategory } = {};\n\nexport const RouteData = {\n selecteduserid: undefined as undefined | number,\n selectedusertab: undefined as undefined | string,\n\n selectedinstanceid: undefined as undefined | number,\n selectedinstanceedittab: undefined as undefined | string,\n\n instancelistpage: undefined as undefined | number,\n loglistpage: undefined as undefined | number,\n byondlistpage: undefined as undefined | number,\n userlistpage: undefined as undefined | number,\n jobhistorypage: new Map(),\n\n oautherrors: [] as InternalError[]\n};\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../node_modules/css-loader/dist/runtime/cssWithMappingToString.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/css-loader/dist/runtime/api.js\";\nimport ___CSS_LOADER_GET_URL_IMPORT___ from \"../node_modules/css-loader/dist/runtime/getUrl.js\";\nimport ___CSS_LOADER_URL_IMPORT_0___ from \"./logo.svg\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".App {\\n background-size: 50%;\\n background: #1e1e1e url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \") no-repeat center;\\n position: absolute;\\n width: 100%;\\n top: 0;\\n bottom: 0;\\n display: grid;\\n}\\n\\n.App-error {\\n color: red;\\n font-size: 150%;\\n margin: auto;\\n}\\n\\n.App-main {\\n display: grid;\\n}\\n\\n.Root {\\n overflow: hidden;\\n display: grid;\\n grid-template-rows: 9% auto;\\n}\\n\\n.Root-login {\\n display: grid;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://./src/App.css\"],\"names\":[],\"mappings\":\"AAAA;IACI,oBAAoB;IACpB,4EAAkD;IAClD,kBAAkB;IAClB,WAAW;IACX,MAAM;IACN,SAAS;IACT,aAAa;AACjB;;AAEA;IACI,UAAU;IACV,eAAe;IACf,YAAY;AAChB;;AAEA;IACI,aAAa;AACjB;;AAEA;IACI,gBAAgB;IAChB,aAAa;IACb,2BAA2B;AAC/B;;AAEA;IACI,aAAa;AACjB\",\"sourcesContent\":[\".App {\\n background-size: 50%;\\n background: #1e1e1e url(logo.svg) no-repeat center;\\n position: absolute;\\n width: 100%;\\n top: 0;\\n bottom: 0;\\n display: grid;\\n}\\n\\n.App-error {\\n color: red;\\n font-size: 150%;\\n margin: auto;\\n}\\n\\n.App-main {\\n display: grid;\\n}\\n\\n.Root {\\n overflow: hidden;\\n display: grid;\\n grid-template-rows: 9% auto;\\n}\\n\\n.Root-login {\\n display: grid;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../../node_modules/css-loader/dist/runtime/cssWithMappingToString.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".tgs-update-notification {\\n color: #66ff07;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://./src/components/AppNavbar.css\"],\"names\":[],\"mappings\":\"AAAA;IACI,cAAc;AAClB\",\"sourcesContent\":[\".tgs-update-notification {\\n color: #66ff07;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","var map = {\n\t\"./Admin/Logs\": [\n\t\t43408,\n\t\t171,\n\t\t408\n\t],\n\t\"./Admin/Logs.tsx\": [\n\t\t43408,\n\t\t171,\n\t\t408\n\t],\n\t\"./Admin/Update\": [\n\t\t80732,\n\t\t171,\n\t\t578,\n\t\t6,\n\t\t724,\n\t\t732\n\t],\n\t\"./Admin/Update.tsx\": [\n\t\t80732,\n\t\t171,\n\t\t578,\n\t\t6,\n\t\t724,\n\t\t732\n\t],\n\t\"./Administration\": [\n\t\t29363,\n\t\t171,\n\t\t363\n\t],\n\t\"./Administration.tsx\": [\n\t\t29363,\n\t\t171,\n\t\t363\n\t],\n\t\"./ChangePassword\": [\n\t\t61304,\n\t\t799\n\t],\n\t\"./ChangePassword.tsx\": [\n\t\t61304,\n\t\t799\n\t],\n\t\"./Configuration\": [\n\t\t67671,\n\t\t671\n\t],\n\t\"./Configuration.tsx\": [\n\t\t67671,\n\t\t671\n\t],\n\t\"./Home\": [\n\t\t59638,\n\t\t638\n\t],\n\t\"./Home.tsx\": [\n\t\t59638,\n\t\t638\n\t],\n\t\"./Info\": [\n\t\t41051,\n\t\t171,\n\t\t51\n\t],\n\t\"./Info.tsx\": [\n\t\t41051,\n\t\t171,\n\t\t51\n\t],\n\t\"./Instance/Create\": [\n\t\t38747,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t578,\n\t\t6,\n\t\t747\n\t],\n\t\"./Instance/Create.tsx\": [\n\t\t38747,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t578,\n\t\t6,\n\t\t747\n\t],\n\t\"./Instance/Edit/ChatBots\": [\n\t\t90740,\n\t\t767,\n\t\t171,\n\t\t740,\n\t\t318\n\t],\n\t\"./Instance/Edit/ChatBots.tsx\": [\n\t\t90740,\n\t\t767,\n\t\t171,\n\t\t740,\n\t\t318\n\t],\n\t\"./Instance/Edit/Config\": [\n\t\t62685,\n\t\t171,\n\t\t685\n\t],\n\t\"./Instance/Edit/Config.tsx\": [\n\t\t62685,\n\t\t171,\n\t\t685\n\t],\n\t\"./Instance/Edit/Deployment\": [\n\t\t44298,\n\t\t899,\n\t\t856,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t356\n\t],\n\t\"./Instance/Edit/Deployment.tsx\": [\n\t\t44298,\n\t\t899,\n\t\t856,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t356\n\t],\n\t\"./Instance/Edit/Engine\": [\n\t\t32240,\n\t\t899,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t657\n\t],\n\t\"./Instance/Edit/Engine.tsx\": [\n\t\t32240,\n\t\t899,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t657\n\t],\n\t\"./Instance/Edit/Files\": [\n\t\t20926,\n\t\t637,\n\t\t171,\n\t\t926,\n\t\t608\n\t],\n\t\"./Instance/Edit/Files.tsx\": [\n\t\t20926,\n\t\t637,\n\t\t171,\n\t\t926,\n\t\t608\n\t],\n\t\"./Instance/Edit/InstancePermissions\": [\n\t\t87345,\n\t\t803,\n\t\t171,\n\t\t345,\n\t\t246\n\t],\n\t\"./Instance/Edit/InstancePermissions.tsx\": [\n\t\t87345,\n\t\t803,\n\t\t171,\n\t\t345,\n\t\t246\n\t],\n\t\"./Instance/Edit/JobHistory\": [\n\t\t25921,\n\t\t171,\n\t\t921\n\t],\n\t\"./Instance/Edit/JobHistory.tsx\": [\n\t\t25921,\n\t\t171,\n\t\t921\n\t],\n\t\"./Instance/Edit/Repository\": [\n\t\t18264,\n\t\t856,\n\t\t611,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t264,\n\t\t233\n\t],\n\t\"./Instance/Edit/Repository.tsx\": [\n\t\t18264,\n\t\t856,\n\t\t611,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t264,\n\t\t233\n\t],\n\t\"./Instance/Edit/Server\": [\n\t\t86046,\n\t\t899,\n\t\t756,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t792\n\t],\n\t\"./Instance/Edit/Server.tsx\": [\n\t\t86046,\n\t\t899,\n\t\t756,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t792\n\t],\n\t\"./Instance/InstanceEdit\": [\n\t\t9182,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t803,\n\t\t767,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t165,\n\t\t240,\n\t\t67,\n\t\t264,\n\t\t740,\n\t\t926,\n\t\t345,\n\t\t182\n\t],\n\t\"./Instance/InstanceEdit.tsx\": [\n\t\t9182,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t803,\n\t\t767,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t165,\n\t\t240,\n\t\t67,\n\t\t264,\n\t\t740,\n\t\t926,\n\t\t345,\n\t\t182\n\t],\n\t\"./Instance/Jobs\": [\n\t\t41818,\n\t\t818\n\t],\n\t\"./Instance/Jobs.tsx\": [\n\t\t41818,\n\t\t818\n\t],\n\t\"./Instance/List\": [\n\t\t70670,\n\t\t171,\n\t\t670\n\t],\n\t\"./Instance/List.tsx\": [\n\t\t70670,\n\t\t171,\n\t\t670\n\t],\n\t\"./Login\": [\n\t\t9310\n\t],\n\t\"./Login.tsx\": [\n\t\t9310\n\t],\n\t\"./Setup\": [\n\t\t12757,\n\t\t666\n\t],\n\t\"./Setup.tsx\": [\n\t\t12757,\n\t\t666\n\t],\n\t\"./User/Create\": [\n\t\t14898,\n\t\t898\n\t],\n\t\"./User/Create.tsx\": [\n\t\t14898,\n\t\t898\n\t],\n\t\"./User/Edit\": [\n\t\t11404,\n\t\t803,\n\t\t171,\n\t\t404\n\t],\n\t\"./User/Edit.tsx\": [\n\t\t11404,\n\t\t803,\n\t\t171,\n\t\t404\n\t],\n\t\"./User/List\": [\n\t\t8746,\n\t\t171,\n\t\t746\n\t],\n\t\"./User/List.tsx\": [\n\t\t8746,\n\t\t171,\n\t\t746\n\t]\n};\nfunction webpackAsyncContext(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\treturn Promise.resolve().then(function() {\n\t\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\t\te.code = 'MODULE_NOT_FOUND';\n\t\t\tthrow e;\n\t\t});\n\t}\n\n\tvar ids = map[req], id = ids[0];\n\treturn Promise.all(ids.slice(1).map(__webpack_require__.e)).then(function() {\n\t\treturn __webpack_require__(id);\n\t});\n}\nwebpackAsyncContext.keys = function() { return Object.keys(map); };\nwebpackAsyncContext.id = 66235;\nmodule.exports = webpackAsyncContext;","var map = {\n\t\"./en.json\": [\n\t\t2422,\n\t\t422\n\t]\n};\nfunction webpackAsyncContext(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\treturn Promise.resolve().then(function() {\n\t\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\t\te.code = 'MODULE_NOT_FOUND';\n\t\t\tthrow e;\n\t\t});\n\t}\n\n\tvar ids = map[req], id = ids[0];\n\treturn __webpack_require__.e(ids[1]).then(function() {\n\t\treturn __webpack_require__.t(id, 3 | 16);\n\t});\n}\nwebpackAsyncContext.keys = function() { return Object.keys(map); };\nwebpackAsyncContext.id = 862;\nmodule.exports = webpackAsyncContext;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = function(chunkId) {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".\" + {\"6\":\"8be2a144a2793570315b\",\"51\":\"018c826056e10797488e\",\"67\":\"d56fce4f5250e06063bf\",\"165\":\"ba83e5a7e5ce7342f32c\",\"171\":\"91936e72280a3d6afb03\",\"182\":\"863e8e49ed9d42931724\",\"233\":\"bca5af88bf85dd9388cf\",\"240\":\"9d4d9c96f62ce48cce5f\",\"246\":\"67f9678139a3c824ae5c\",\"264\":\"6bc80619e126b48208c1\",\"318\":\"262f7ca0e51a961f2197\",\"345\":\"463f4581d9005d5a1f8f\",\"356\":\"8162c10f9d74465626fb\",\"363\":\"d935e8c7446b49ba6955\",\"370\":\"d23700bdb9e4969823c6\",\"404\":\"a8f6c9632768de417c6c\",\"408\":\"67a24726289357584bda\",\"422\":\"6ffa6fbbf00a7aa4137e\",\"578\":\"b30e90acaefb17f3a915\",\"608\":\"cdba29b0c970b4473f8d\",\"611\":\"9c4e031ea3c5676ddc02\",\"637\":\"67f97d250d5cc7c88b41\",\"638\":\"22ae8c317d679c0fbe03\",\"657\":\"ebfcf366f770219cda87\",\"666\":\"466319a2ec0bfe75619b\",\"670\":\"f23545dfb1fa34187665\",\"671\":\"d70d3c2a0ea3e94cdaa3\",\"685\":\"d842923a8b4fb2925914\",\"724\":\"37608abcbb4a745b53ee\",\"732\":\"26d1b1972e8409d84e2b\",\"740\":\"ff042fc269d749115529\",\"746\":\"24e79402e2a97d9d230f\",\"747\":\"a7a7de444f0f31df471a\",\"756\":\"3ea7efa9992f327c4f5e\",\"757\":\"cd59822a9cafc5472de2\",\"767\":\"2226708e286478875491\",\"792\":\"d0502c03902c2b81a3bc\",\"799\":\"5f592f7ea70157f79ee3\",\"803\":\"e84dc0cafa5ef825835a\",\"818\":\"de10c00448eedd21537d\",\"856\":\"4c7ce7828aeda2beaebd\",\"898\":\"506fa6125431a4f5701d\",\"899\":\"40a4d85a43cf77edbe66\",\"921\":\"d5768a750d900f116d8c\",\"926\":\"9721f261bd7f5a7449df\"}[chunkId] + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/\";","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t179: 0\n};\n\n__webpack_require__.f.j = function(chunkId, promises) {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = function(event) {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t} else installedChunks[chunkId] = 0;\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunktgstation_server_control_panel\"] = self[\"webpackChunktgstation_server_control_panel\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [607,338,340], function() { return __webpack_require__(16143); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","leafPrototypes","getProto","inProgress","dataWebpackPrefix","AccessDenied","React","render","title","variant","className","onClick","this","props","history","goBack","id","withRouter","ErrorAlert","Component","constructor","super","state","popup","error","handleClose","setState","dismissible","onClose","code","centered","show","onHide","size","closeButton","desc","type","DescType","VERSION","MODE","API_VERSION","extendedInfo","replace","addError","setErrors","prevState","errors","Array","from","push","displayErrors","map","err","index","key","prev","newarr","undefined","GenericAlert","body","children","JobError","open","setOpen","useState","Button","values","info","job","errorCode","TGSErrorCode","Modal","description","exceptionDetails","JobCard","createddate","Date","startedAt","createddiff","getTime","now","stoppeddiff","stoppedAt","cancelled","Toast","style","maxWidth","width","ToastHeader","ToastBody","stage","OverlayTrigger","overlay","Tooltip","toLocaleString","ref","triggerHandler","value","numeric","updateIntervalInSeconds","startedBy","name","cancelledBy","height","ProgressBar","animated","label","progress","toString","striped","canCancel","padding","onCancel","icon","JobsList","widgetRef","currentTimeout","handleUpdate","bind","jobs","JobsController","nextRetrySeconds","ownerrors","loading","instances","Map","current","scrollTop","componentDidMount","componentWillUnmount","clearTimeout","getSeconds","setTimeout","widget","nested","display","totalJobs","configOptions","jobsWidgetOptions","length","position","top","bottom","right","left","pointerEvents","zIndex","default","x","document","documentElement","clientWidth","Math","min","y","clientHeight","minHeight","minWidth","bounds","text","seconds","sort","a","b","instanceid","jobMap","xFinishedEnabled","forEach","instanceHeaderStyle","marginTop","marginLeft","renderTooltip","get","amount","placement","faTimes","defaultProps","Loading","animation","center","widthUnit","otherprops","styles","appear","classNames","addEndListener","node","done","addEventListener","Login","submit","console","log","RouteData","busy","validated","username","password","window","sessionStorage","getItem","CredentialsProvider","CredentialsType","tryLoginDefault","loggedOut","ServerClient","StatusCode","redirectSetup","context","serverInfo","providers","OAuthProvider","faGithub","faDiscord","src","alt","faInvision","providersTheme","GitHub","Discord","TGForums","Keycloak","InvisionCommunity","Col","lg","md","Card","Form","onSubmit","controlId","placeholder","onChange","event","target","required","block","oAuthProviderInfos","Object","keys","provider","ptheme","background","startOAuth","InternalError","ErrorCode","jsError","Error","stateArray","Uint8Array","crypto","getRandomValues","dec","padStart","join","url","e","encodeURIComponent","clientId","redirectUri","serverUrl","oauthdata","JSON","parse","location","pathname","setItem","stringify","href","Promise","resolve","preventDefault","response","userName","contextType","GeneralContext","_API_VERSION","_VERSION","_MODE","_DEFAULT_BASEPATH","DEFAULT_BASEPATH","_DEFAULT_APIPATH","DEFAULT_APIPATH","publicPath","__webpack_public_path__","options","AppNavbar","logoutClick","loginSuccess","logout","refresh","routes","categories","AppCategories","updateAvailable","checkShowServerUpdateIcon","userResponse","UserClient","user","payload","permissionSet","resolvePermissionSet","hasAdminRight","AdministrationRights","AdminClient","latestVersion","SemVer","currentVersion","version","compare","LoginHooks","RouteController","Navbar","expand","loggedIn","collapseOnSelect","bg","AppRoutes","reload","renderVersion","Nav","cat","leader","cachedAuth","link","route","active","matchesPath","navbarLoose","NavDropdown","filter","val","catleader","visibleNavbar","faExclamationCircle","renderUser","Dropdown","alignRight","Logo","memeSelector","onToggle","showing","round","random","ReportIssue","faExclamationTriangle","ErrorBoundary","componentDidUpdate","prevProps","errorInfo","componentDidCatch","Container","border","message","as","componentStack","Reload","clear","match","path","LoadSpin","page","NotFound","loadable","fallback","Router","refreshListener","components","set","file","URLSearchParams","search","listen","listener","URLSearch","replaceState","oauthstate","setErrorAndEnd","token","removeItem","category","selectCategory","loginless","exact","loose","Comp","noContainer","Locales","en","Translation","locale","messages","TranslationFactory","split","localization","shortHandedLocale","getShortHandedLocale","fallbackLocale","loadTranslation","model","InnerApp","Pkg","ctrlKey","shiftKey","basename","URL","passdownCat","Alert","idx","deleteError","App","translationFactory","finishLogin","finishLogout","updateContextUser","updateContextServer","deleteGeneralContextError","GeneralContextInfo","Set","newSet","add","lastError","delete","then","loggedInSuccessfully","translationError","translation","defaultLocale","time","timeEnd","IndexApp","library","faCheck","faUser","faUserSlash","faHdd","faSync","faPlus","faQuestion","faHome","faTools","faCogs","faUndo","faInfo","faGripLinesVertical","faAngleRight","faKey","faPen","faTrash","faInfoCircle","faGitAlt","faHammer","faListUl","faComments","faFolderOpen","faUsers","faCodeBranch","faSearch","faServer","faStream","faLock","faMinus","faUnlock","faCaretRight","faCaretDown","faComment","faHashtag","faFolderPlus","faFolderMinus","faFile","faFileAlt","faClipboard","faArrowLeft","faUpload","faGamepad","ConfigController","loadedChannelFromWebpack","alert","localStorage","mountApp","ReactDOM","getElementById","readyState","TypedEmitter","refreshing","rtcontroller","refreshRoutes","catch","catmap","entries","UnpopulatedAppCategories","work","getImmediateRoutes","isAuthorized","auth","all","emit","routesNoAuth","getRoutes","wait4refresh","on","results","download","filename","element","createElement","setAttribute","appendChild","click","removeChild","replaceAll","str","find","ignore","RegExp","slice","pathToRegexp","end","test","group","bitflagIsTrue","bitfield","bitflag","administrationRights","hasInstanceManagerRight","instanceManagerRights","hasEngineRight","engineRights","hasChatBotRight","chatBotRights","hasDreamDaemonRight","dreamDaemonRights","hasDreamMakerRight","dreamMakerRights","hasInstancePermRight","instancePermissionSetRights","hasRepoRight","repositoryRights","hasFilesRight","configurationRights","adminRight","async","instanceManagerRight","home","homeIcon","instancecreate","InstanceManagerRights","instancelist","instanceedit","selectedinstanceid","selectedinstanceedittab","instancejobs","userlist","useredit","selecteduserid","selectedusertab","usercreate","admin","admin_update","admin_logs","passwd","config","setup","oAuth","instance","instancelistpage","loglistpage","byondlistpage","userlistpage","jobhistorypage","oautherrors","___CSS_LOADER_EXPORT___","___CSS_LOADER_URL_REPLACEMENT_0___","module","webpackAsyncContext","req","__webpack_require__","o","ids","exports","t","__webpack_module_cache__","moduleId","cachedModule","__webpack_modules__","call","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","fulfilled","j","every","splice","r","n","getter","__esModule","d","getPrototypeOf","obj","__proto__","mode","ns","create","def","indexOf","getOwnPropertyNames","definition","defineProperty","enumerable","f","chunkId","reduce","promises","u","g","globalThis","Function","prop","prototype","hasOwnProperty","l","script","needAttach","scripts","getElementsByTagName","s","getAttribute","charset","timeout","nc","onScriptComplete","onerror","onload","doneFns","parentNode","head","Symbol","toStringTag","p","installedChunks","installedChunkData","promise","reject","errorType","realSrc","request","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/webpanel/5.7.0/main.23756f40ad9814a1e9eb.bundle.js b/webpanel/5.7.0/main.23756f40ad9814a1e9eb.bundle.js deleted file mode 100644 index 9966c94f..00000000 --- a/webpanel/5.7.0/main.23756f40ad9814a1e9eb.bundle.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(){var e,t,n,r,o,s={97888:function(e,t,n){"use strict";var r=n(67294),o=n(35005),s=n(44012),a=n(5977),i=n(9635);class l extends r.Component{render(){return r.createElement(i.Z,{title:"generic.accessdenied"},r.createElement(o.Z,{variant:"danger",className:"float-right",onClick:()=>{this.props.history.goBack()}},r.createElement(s.Z,{id:"generic.goback"})))}}t.Z=(0,a.EN)(l)},3e3:function(e,t,n){"use strict";n.d(t,{hP:function(){return h},iT:function(){return m}});var r=n(28359),o=n(67294),s=n(88375),a=n(35005),i=n(37959),l=n(44012),c=n(96846),d=n(86755);class u extends o.Component{constructor(e){super(e),this.state={popup:!1}}render(){if(!this.props.error)return"";const e=()=>this.setState({popup:!1});return o.createElement(s.Z,{className:"clearfix",variant:"error",dismissible:!!this.props.onClose,onClose:this.props.onClose},o.createElement(l.Z,{id:this.props.error.code||"error.app.undefined"}),o.createElement("hr",null),o.createElement(a.Z,{variant:"danger",className:"float-right",onClick:()=>this.setState({popup:!0})},o.createElement(l.Z,{id:"generic.details"})),o.createElement(i.Z,{centered:!0,show:this.state.popup,onHide:e,size:"lg"},o.createElement(i.Z.Header,{closeButton:!0},o.createElement(i.Z.Title,null,o.createElement(l.Z,{id:this.props.error.code||"error.app.undefined"}))),o.createElement(i.Z.Body,{className:"text-danger pb-0"},this.props.error.desc?.type===c._T.LOCALE?o.createElement(l.Z,{id:this.props.error.desc.desc||"error.api.empty"}):this.props.error.desc?.desc?this.props.error.desc.desc:"",o.createElement("hr",null),o.createElement(r.Z,null,o.createElement("code",{className:"bg-darker d-block pre-wrap p-2 pre-scrollable"},`Webpanel Version: ${d.q4}\nWebpanel Mode: ${d.IK}\nAPI Version: ${d.Gn}\n\nError Code: ${this.props.error.code}\nError Description: ${this.props.error.desc?this.props.error.desc.desc:"No description"}\n\nAdditional Information:\n${this.props.error.extendedInfo}`.replace(/\\/g,"\\\\")))),o.createElement(i.Z.Footer,null,o.createElement("span",{className:"font-italic mr-auto"},o.createElement(l.Z,{id:"generic.debugwarn"})),o.createElement(a.Z,{variant:"secondary",onClick:e},o.createElement(l.Z,{id:"generic.close"})))))}}function m([,e],t){e((e=>{const n=Array.from(e);return n.push(t),n}))}function h([e,t]){return e.map(((e,n)=>{if(e)return o.createElement(u,{key:n,error:e,onClose:()=>t((e=>{const t=Array.from(e);return t[n]=void 0,t}))})}))}t.ZP=u},9635:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(67294),o=n(88375),s=n(44012);function a(e){return r.createElement(o.Z,{className:"clearfix",variant:"error"},r.createElement(s.Z,{id:e.title}),e.body?r.createElement(r.Fragment,null,r.createElement("hr",null),r.createElement(s.Z,{id:e.body})):e.children?r.createElement(r.Fragment,null,r.createElement("hr",null),e.children):null)}},93128:function(e,t,n){"use strict";n.d(t,{Z:function(){return b}});var r=n(67814),o=n(67294),s=n(35005),a=n(15293),i=n(51479),l=n(10729),c=n(61318),d=n(17863),u=n(43489),m=n(44012),h=n(48272),p=n(28359),f=n(37959),g=n(48509);function v(e){const[t,n]=(0,o.useState)(!1);return o.createElement(o.Fragment,null,o.createElement(s.Z,{variant:"danger",className:"d-inline-block",onClick:()=>n(!0),size:"sm"},o.createElement(m.Z,{id:"generic.errordetails",values:{info:void 0!==e.job.errorCode&&null!==e.job.errorCode?g.jK[e.job.errorCode]:"NoCode"}})),o.createElement(f.Z,{centered:!0,show:t,onHide:()=>n(!1),size:"lg"},o.createElement(f.Z.Header,{closeButton:!0},o.createElement(f.Z.Title,null,o.createElement(m.Z,{id:e.job.description}))),o.createElement(f.Z.Body,{className:"text-danger pb-0"},o.createElement(m.Z,{id:"view.instance.jobs.error"}),":"," ",void 0!==e.job.errorCode&&null!==e.job.errorCode?g.jK[e.job.errorCode]:"NoCode",o.createElement("hr",null),o.createElement(p.Z,null,o.createElement("code",{className:"bg-darker d-block pre-wrap p-2 pre-scrollable"},e.job.exceptionDetails))),o.createElement(f.Z.Footer,null,o.createElement(s.Z,{variant:"secondary",onClick:()=>n(!1)},o.createElement(m.Z,{id:"generic.close"})))))}function E(){return E=Object.assign||function(e){for(var t=1;t{this.props.onClose&&this.props.onClose(e)}},o.createElement(d.Z,{closeButton:!!e.stoppedAt&&!!this.props.onClose,className:`bg-${f}`},"#",e.id,": ",e.description),o.createElement(c.Z,{className:"pt-1 text-white"},e.stage?o.createElement("div",{className:"mb-2"},"\u25b6",e.stage):null,o.createElement(m.Z,{id:"app.job.started"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-started`},t.toLocaleString())},(({ref:e,...t})=>o.createElement("span",E({},t,{ref:e}),o.createElement(h.Z,{value:n,numeric:"auto",updateIntervalInSeconds:1})))),o.createElement("br",null),o.createElement(m.Z,{id:"app.job.startedby"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-startedby`},o.createElement(m.Z,{id:"generic.userid"}),e.startedBy.id)},(({ref:t,...n})=>o.createElement("span",E({ref:t},n),e.startedBy.name))),o.createElement("br",null),o.createElement("br",null),e.stoppedAt?o.createElement(o.Fragment,null,o.createElement(m.Z,{id:e.cancelled?"app.job.cancelled":"app.job.completed"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-stopped`},t.toLocaleString())},(({ref:e,...t})=>o.createElement("span",E({},t,{ref:e}),o.createElement(h.Z,{value:p,numeric:"auto",updateIntervalInSeconds:1})))),o.createElement("br",null)):"",e.cancelledBy?o.createElement(o.Fragment,null,o.createElement(m.Z,{id:"app.job.cancelledby"}),o.createElement(a.Z,{overlay:o.createElement(u.Z,{id:`${e.id}-tooltip-createdby`},o.createElement(m.Z,{id:"generic.userid"}),e.startedBy.id)},(({ref:t,...n})=>o.createElement("span",E({ref:t},n),e.cancelledBy.name))),o.createElement("br",null)):"",void 0!==e.errorCode||void 0!==e.exceptionDetails?o.createElement(v,{job:e}):"",o.createElement("div",{className:"d-flex mt-2",style:{height:"1.5rem"}},o.createElement(i.Z,{className:"text-darker font-weight-bold flex-grow-1 h-unset",animated:!e.stoppedAt,label:"number"==typeof e.progress?`${e.progress.toString()}%`:void 0,now:"number"==typeof e.progress?e.progress:100,striped:!0,variant:f}),e.canCancel&&!e.stoppedAt?o.createElement(s.Z,{style:{padding:"0 1em"},className:"ml-1",variant:"danger",onClick:()=>this.props.onCancel(e)},o.createElement(r.G,{icon:"times",className:"d-block"})):null)))}}},20271:function(e,t,n){"use strict";n.d(t,{Z:function(){return E}});var r=n(51436),o=n(67814),s=n(67294),a=n(15881),i=n(35005),l=n(15293),c=n(43489),d=n(44012),u=n(61090),m=n(27961),h=n(39521),p=n(3e3),f=n(93128),g=n(35855);function v(){return v=Object.assign||function(e){for(var t=1;t{const n=Array.from(t.ownerrors);return n.push(e),this.widgetRef.current&&(this.widgetRef.current.scrollTop=0),{ownerrors:n}}))}componentDidMount(){h.Z.on("jobsLoaded",this.handleUpdate),this.handleUpdate()}componentWillUnmount(){h.Z.removeListener("jobsLoaded",this.handleUpdate)}handleUpdate(){let e;this.currentTimeout&&(clearTimeout(this.currentTimeout),this.currentTimeout=null),h.Z.nextRetry?(e=h.Z.nextRetry.getSeconds()>(new Date).getSeconds()?h.Z.nextRetry.getSeconds()-(new Date).getSeconds():0,this.currentTimeout=setTimeout((()=>this.handleUpdate()),1e3)):e=null,this.setState({jobs:h.Z.jobsByInstance,errors:h.Z.errors,nextRetrySeconds:e,loading:!1,instances:h.Z.accessibleInstances})}async onCancel(e){await h.Z.cancelJob(e.id,(e=>this.addError(e)))&&(h.Z.fastmode=5)}onClose(e){h.Z.clearJob(e.id)}render(){if(!this.props.widget)return this.nested();let e,t=0;for(const e of this.state.jobs.values())t+=e.size;return e=m.ZP.jobswidgetdisplay.value!==m.zQ.NEVER&&(m.ZP.jobswidgetdisplay.value===m.zQ.ALWAYS||(t>0||this.state.errors.length>0)),s.createElement("div",{style:{position:"fixed",top:0,bottom:0,right:0,left:0,pointerEvents:"none",zIndex:5}},s.createElement(u.s,{className:"jobswidget "+(e?"":"invisible"),style:{pointerEvents:"auto",bottom:0,right:0},default:{width:"30vw",height:"50vh",x:document.documentElement.clientWidth-Math.min(.3*document.documentElement.clientWidth,350)-20,y:document.documentElement.clientHeight-.5*document.documentElement.clientHeight-20},maxWidth:350,minHeight:50,minWidth:110,bounds:"parent"},s.createElement("div",{className:"fancyscroll overflow-auto h-100",ref:this.widgetRef},s.createElement("h5",{className:"text-center text-darker font-weight-bold"},s.createElement(d.Z,{id:"view.instance.jobs.title"})),this.nested())))}nested(){return s.createElement("div",{className:this.props.widget?"d-sm-block":""},this.state.loading?s.createElement(g.Z,{text:"loading.instance.jobs.list"}):"",this.state.ownerrors.map(((e,t)=>{if(e)return s.createElement(p.ZP,{key:t,error:e,onClose:()=>this.setState((e=>{const n=Array.from(e.ownerrors);return n[t]=void 0,{ownerrors:n}}))})})),this.state.errors.length>0?s.createElement(s.Fragment,null,this.state.errors.map(((e,t)=>s.createElement("div",{key:t,style:{maxWidth:this.props.widget?350:"unset"}},s.createElement(p.ZP,{error:e})))),s.createElement(a.Z,null,0===this.state.nextRetrySeconds?s.createElement(d.Z,{id:"view.instance.jobs.reconnect_now"}):null!=this.state.nextRetrySeconds?s.createElement(d.Z,{id:"view.instance.jobs.reconnect_in",values:{seconds:this.state.nextRetrySeconds}}):s.createElement(d.Z,{id:"view.instance.jobs.reconnected_auth"}))):null,Array.from(this.state.jobs).sort(((e,t)=>e[0]-t[0])).map((([e,t])=>{let n=!1;t.forEach((e=>{e.stoppedAt&&(n=!0)}));const a=n?{marginTop:"5px",marginLeft:"20px"}:void 0;return s.createElement(s.Fragment,{key:e},s.createElement("div",{className:"bg-dark p-2 row"},s.createElement("div",{className:`col-${n?9:12} text-center`},s.createElement("div",{style:a},s.createElement(l.Z,{overlay:(e=>t=>s.createElement(c.Z,v({id:`tooltip-instance-${e}`},t),e))(e)},s.createElement(s.Fragment,null,this.state.instances.get(e)?.name??"Unknown"," ","(",s.createElement(d.Z,{id:"view.instance.jobs.jobtotal",values:{amount:t.size}}),")")))),n?s.createElement("div",{className:"col-3 text-right"},s.createElement(l.Z,{placement:"top",overlay:e=>s.createElement(c.Z,v({id:"clear-instance-jobs"},e),s.createElement(d.Z,{id:"view.instance.jobs.clearfinished"}))},s.createElement(i.Z,{variant:"outline-secondary",onClick:()=>t.forEach((e=>{e.stoppedAt&&h.Z.clearJob(e.id)})),className:"nowrap"},s.createElement(o.G,{icon:r.NBC})))):s.createElement(s.Fragment,null)),Array.from(t,(([,e])=>e)).sort(((e,t)=>t.id-e.id)).map((e=>s.createElement(f.Z,{job:e,width:this.props.width,key:e.id,onClose:this.onClose,onCancel:this.onCancel}))))})))}}E.defaultProps={widget:!0}},35855:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var r=n(67294),o=n(36968),s=n(44012),a=n(29697),i=n(94537);function l(){return l=Object.assign||function(e){for(var t=1;t{e.addEventListener("transitionend",t,!1)}},r.createElement("div",{className:n?"text-center":""},r.createElement(o.Z,l({variant:e||"secondary",className:n?`d-block mx-auto ${c??""}`:c,style:f,animation:t||"border"},p)),m?r.createElement(s.Z,{id:m}):"",h)))}}c.defaultProps={animation:"border",width:"50",widthUnit:"vmin",center:!0}},9310:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return x}});var r=n(51417),o=n(16566),s=n(38658),a=n(67814),i=n(67294),l=n(35005),c=n(31555),d=n(15881),u=n(32258),m=n(44012),h=n(5977),p=n(48509),f=n(11895),g=n(96846),v=n(53803),E=n(75631),b=n(42522),y=n(44615),w=n(86755),Z=n(1320),I=n(3e3),C=n(35855);class A extends i.Component{constructor(e){super(e),this.submit=this.submit.bind(this),console.log(Z.Mq.oautherrors),this.state={busy:!1,validated:!1,username:"",password:"",errors:Array.from(Z.Mq.oautherrors)}}async componentDidMount(){(window.sessionStorage.getItem("oauth")??b.Z.credentials?.type===f.P.OAuth)||"GITHUB"!==w.IK||await this.tryLoginDefault()}async tryLoginDefault(){if(this.props.loggedOut)return;(await E.Z.login(b.Z.default)).code===v.G.OK&&this.setState({redirectSetup:!0})}addError(e){this.setState((t=>{const n=Array.from(t.errors);return n.push(e),{errors:n}}))}render(){if(this.state.busy||b.Z.hasToken())return i.createElement(C.Z,{text:"loading.login"});if(!this.context.serverInfo)return i.createElement(C.Z,{text:"loading.serverinfo"});const e={[p.O4.GitHub]:i.createElement(a.G,{icon:s.zh,style:{width:"1.2em"}}),[p.O4.Discord]:i.createElement(a.G,{icon:o.om,style:{width:"1.2em"}}),[p.O4.TGForums]:i.createElement("img",{src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/c97e39e417e48a3282f9.svg",alt:"tglogo",style:{width:"1.2em"}}),[p.O4.Keycloak]:i.createElement("img",{src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/995a88a72fd6520c8505.png",alt:"keycloaklogo",style:{width:"1.2em"}}),[p.O4.InvisionCommunity]:i.createElement(a.G,{icon:r.n5f,style:{width:"1.2em"}})},t={GitHub:"#161b22",Discord:"#7289da",TGForums:void 0,Keycloak:void 0,InvisionCommunity:void 0};return i.createElement(c.Z,{className:"mx-auto",lg:5,md:8},this.state.errors.map(((e,t)=>{if(e)return i.createElement(I.ZP,{key:t,error:e,onClose:()=>this.setState((e=>{const n=Array.from(e.errors);return n[t]=void 0,{errors:n}}))})})),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.header"})),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.type.generic"})),i.createElement(u.Z,{validated:this.state.validated,onSubmit:this.submit},i.createElement(u.Z.Group,{controlId:"username"},i.createElement(u.Z.Label,null,i.createElement(m.Z,{id:"login.username"})),i.createElement(u.Z.Control,{type:"text",placeholder:"Enter username",onChange:e=>this.setState({username:e.target.value}),value:this.state.username,required:!0})),i.createElement(u.Z.Group,{controlId:"password"},i.createElement(u.Z.Label,null,i.createElement(m.Z,{id:"login.password"})),i.createElement(u.Z.Control,{type:"password",placeholder:"Password",onChange:e=>this.setState({password:e.target.value}),value:this.state.password,required:!0})),i.createElement(l.Z,{type:"submit",block:!0},i.createElement(m.Z,{id:"login.submit"})))),(this.context.serverInfo?.oAuthProviderInfos?.Discord||this.context.serverInfo?.oAuthProviderInfos?.GitHub||this.context.serverInfo?.oAuthProviderInfos?.Keycloak||this.context.serverInfo?.oAuthProviderInfos?.TGForums||this.context.serverInfo?.oAuthProviderInfos?.InvisionCommunity)&&i.createElement(i.Fragment,null,i.createElement("hr",null),i.createElement(d.Z,{body:!0},i.createElement(d.Z.Title,null,i.createElement(m.Z,{id:"login.type.oauth"})),Object.keys(this.context.serverInfo.oAuthProviderInfos??{}).map((n=>{const r=t[n];return i.createElement(l.Z,{key:n,block:!0,style:r?{background:r}:void 0,onClick:()=>this.startOAuth(n)},e[n],i.createElement("span",{className:"ml-1"},i.createElement(m.Z,{id:"login.oauth",values:{provider:n}})))}))))))}async startOAuth(e){if(!this.context.serverInfo)return void this.addError(new g.ZP(g.jK.APP_FAIL,{jsError:Error("serverInfo is null in startOAuth")}));const t=new Uint8Array(10);window.crypto.getRandomValues(t);const n=Array.from(t,(e=>e.toString(16).padStart(2,"0"))).join("");let r;const o=encodeURIComponent;switch(e){case p.O4.Discord:r=`https://discord.com/api/oauth2/authorize?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.Discord.clientId)}&scope=identify&state=${o(n)}`;this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri&&(r=`${r}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri)}`);break;case p.O4.GitHub:r=`https://github.com/login/oauth/authorize?client_id=${o(this.context.serverInfo.oAuthProviderInfos.GitHub.clientId)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.GitHub.redirectUri)}&state=${o(n)}&allow_signup=false`;break;case p.O4.Keycloak:r=`${this.context.serverInfo.oAuthProviderInfos.Keycloak.serverUrl}/protocol/openid-connect/auth?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.Keycloak.clientId)}&scope=openid&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.Keycloak.redirectUri)}`;break;case p.O4.TGForums:r=`https://tgstation13.org/phpBB/app.php/tgapi/oauth/auth?scope=user&client_id=${o(this.context.serverInfo.oAuthProviderInfos.TGForums.clientId)}&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.TGForums.redirectUri)}`;break;case p.O4.InvisionCommunity:r=`${this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.serverUrl}/oauth/authorize/?response_type=code&client_id=${o(this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.clientId)}&scope=profile&state=${o(n)}&redirect_uri=${o(this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.redirectUri)}`}const s=JSON.parse(window.sessionStorage.getItem("oauth")??"{}");return s[n]={provider:e,url:this.props.location.pathname},window.sessionStorage.setItem("oauth",JSON.stringify(s)),window.location.href=r,new Promise((e=>e()))}async submit(e){e.preventDefault(),this.setState({busy:!0});const t=await E.Z.login({type:f.P.Password,userName:this.state.username,password:this.state.password});t.code==v.G.ERROR&&(this.setState({busy:!1}),this.addError(t.error))}}A.contextType=y.f;var x=(0,h.EN)(A)},44615:function(e,t,n){"use strict";n.d(t,{f:function(){return r}});const r=n(67294).createContext(void 0)},86755:function(e,t,n){"use strict";n.d(t,{Gn:function(){return r},IK:function(){return s},UG:function(){return a},cV:function(){return i},q4:function(){return o}});const r="10.3.0",o="v5.7.0",s="GITHUB",a="/app/",i="/"},16143:function(e,t,n){"use strict";window.publicPath,console.log("Public path:",n.p);n(51340),n(75029),n(5547),n(74462),n(17689);var r=n(73935),o=n(57806),s=n(39521),a=n(93379),i=n.n(a),l=n(39087),c={insert:"head",singleton:!1},d=(i()(l.Z,c),l.Z.locals,n(67294)),u=n(88375),m=n(10682),h=n(44012),p=n(29558),f=n(73727),g="5.7.0",v=n(96846),E=n(53803),b=n(75631),y=n(16942),w=n(42522),Z=n(50452),I=n(58007),C={insert:"head",singleton:!1},A=(i()(I.Z,C),I.Z.locals,n(51436)),x=n(67814),k=n(24214),S=n(15293),j=n(43489),L=n(35005),N=n(75040),O=n(13361),P=n(95602),$=n(5977),U=n(81249),G=n(22480),R=n(48509),T=n(44615),_=n(16964),D=n(70601),B=n(1320);function F(){return F=Object.assign||function(e){for(var t=1;t{this.props.history.push(B.$w.home.link??B.$w.home.route,{reload:!0})},className:"mr-auto"},this.renderVersion()),d.createElement(P.Z.Toggle,{className:"mr-2","aria-controls":"responsive-navbar-nav"}),d.createElement(P.Z.Collapse,{className:"text-right mr-2",style:{minWidth:"0px"}},d.createElement(O.Z,null,this.props.loggedIn?Object.values(this.state.categories).map((e=>{if(e.leader.cachedAuth)return 1==e.routes.length?d.createElement(O.Z.Item,{key:e.name},d.createElement(O.Z.Link,{onClick:()=>{this.props.history.push(e.leader.link??e.leader.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.leader.route,!e.leader.navbarLoose)},d.createElement(h.Z,{id:e.leader.name}))):d.createElement(O.Z.Item,{key:e.name},d.createElement(k.Z,{id:e.name+"-dropdown",title:d.createElement(h.Z,{id:e.leader.name})},Object.values(e.routes).filter((e=>e.cachedAuth)).length>=2?d.createElement(d.Fragment,null,d.createElement(k.Z.Item,{onClick:()=>{this.props.history.push(e.leader.link??e.leader.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.leader.route,!0)},d.createElement(h.Z,{id:e.leader.name})),e.routes.map((e=>{if(!e.catleader&&e.cachedAuth&&e.visibleNavbar)return d.createElement(k.Z.Item,{key:e.name,onClick:()=>{this.props.history.push(e.link??e.route,{reload:!0})},active:(0,_.JW)(this.props.location.pathname,e.route,!e.navbarLoose)},d.createElement(h.Z,{id:e.name}))}))):""))})):d.createElement(O.Z.Item,null,d.createElement(O.Z.Link,{onClick:()=>{this.props.history.push(B.$w.home.link??B.$w.home.route,{reload:!0})},active:!0},d.createElement(h.Z,{id:"routes.login"})))),this.state.updateAvailable?d.createElement(S.Z,{placement:"right",overlay:e=>d.createElement(j.Z,F({id:"tgs-updated-tooltip"},e),d.createElement(h.Z,{id:"navbar.update"}))},d.createElement("h3",null,d.createElement(x.G,{className:"tgs-update-notification",onClick:()=>this.props.history.push(B.$w.admin_update.link??B.$w.admin_update.route,{reload:!0}),icon:A.RLE}))):d.createElement(d.Fragment,null),this.renderUser())))}renderVersion(){return this.context.serverInfo?.version?d.createElement(d.Fragment,null,d.createElement(h.Z,{id:"generic.appname"})," v",this.context.serverInfo.version):d.createElement(h.Z,{id:"loading.loading"})}renderUser(){return this.props.loggedIn?d.createElement(O.Z.Item,{className:"ml-auto"},d.createElement(N.Z,null,d.createElement(N.Z.Toggle,{id:"user-dropdown",type:"button",variant:"primary","data-toggle":"dropdown","aria-haspopup":"true","aria-expanded":"false"},this.context.user?this.context.user.name:d.createElement(h.Z,{id:"loading.loading"})),d.createElement(N.Z.Menu,{alignRight:!0,className:"text-right"},d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.info.link??B.$w.info.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.info"})),d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.config.link??B.$w.config.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.config"})),B.$w.passwd.cachedAuth?d.createElement(N.Z.Item,{onClick:()=>{this.props.history.push(B.$w.passwd.link??B.$w.passwd.route,{reload:!0})}},d.createElement(h.Z,{id:"routes.passwd"})):"",d.createElement(N.Z.Item,{onClick:()=>{b.Z.emit("purgeCache"),this.props.history.replace(this.props.location.pathname,{reload:!0})}},d.createElement(h.Z,{id:"navbar.purgecache"})),d.createElement(N.Z.Item,{onClick:()=>{this.props.history.replace(this.props.location.pathname,{reload:!0})}},d.createElement(h.Z,{id:"navbar.refresh"})),d.createElement(N.Z.Item,{onClick:this.logoutClick},d.createElement(h.Z,{id:"navbar.logout"}))))):d.createElement(d.Fragment,null,d.createElement(L.Z,{onClick:()=>{this.props.history.push(B.$w.config.link??B.$w.config.route,{reload:!0})},variant:"primary"},d.createElement(x.G,{icon:"cogs"})),d.createElement(L.Z,{onClick:()=>{this.props.history.push(B.$w.info.link??B.$w.info.route,{reload:!0})},variant:"primary"},d.createElement(x.G,{icon:"info-circle"})))}logoutClick(){b.Z.logout()}}M.contextType=T.f;var z=(0,$.EN)(M);function K(){return K=Object.assign||function(e){for(var t=1;t{t&&(e=Math.round(100*Math.random())%26)},overlay:t=>d.createElement(j.Z,K({id:"report-issue-tooltip"},t),d.createElement(h.Z,{id:`view.meme_${e}`}))},d.createElement("img",{className:"nowrap corner-logo",width:50,height:50,src:"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/b5616c99bf2052a6bbd7.svg"}))}}function W(){return W=Object.assign||function(e){for(var t=1;td.createElement(j.Z,W({id:"report-issue-tooltip"},e),d.createElement(h.Z,{id:"view.report"}))},d.createElement(L.Z,{className:"nowrap report-issue",onClick:()=>window.open("https://github.com/tgstation/tgstation-server-webpanel/issues/new")},d.createElement(x.G,{icon:A.eHv})))}}var V=n(3e3),q=n(15881),Y=n(86755);class X extends d.Component{constructor(e){super(e),this.state={}}componentDidUpdate(e){this.props.location.key!==e.location.key&&this.setState({error:void 0,errorInfo:void 0})}componentDidCatch(e,t){this.setState({error:e,errorInfo:t})}render(){return this.state.error?d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(q.Z,{className:"bg-transparent",border:"danger"},d.createElement(q.Z.Header,{className:"bg-danger"},d.createElement(h.Z,{id:"error.somethingwentwrong"})),d.createElement(q.Z.Body,null,d.createElement(q.Z.Title,null,this.state.error.name,": ",this.state.error.message),d.createElement(q.Z.Text,{as:"pre",className:"bg-transparent text-danger"},d.createElement("code",null,`Webpanel Version: ${Y.q4}\nWebpanel Mode: ${Y.IK}\nStack trace: ${this.state.errorInfo?.componentStack??"Unable to get stack info"}`))))):this.props.children}}var Q=(0,$.EN)(X),ee=n(20271),te=n(35855),ne=n(40684),re=n(11895),oe=n(97888);class se extends d.Component{constructor(e){super(e),this.state={clear:!1}}componentDidUpdate(e){this.state.clear?this.setState({clear:!1}):e.match.path==this.props.match.path&&e.location.key!=this.props.location.key&&this.props.location.state?.reload&&this.setState({clear:!0})}render(){return this.state.clear?"":this.props.children}}var ae=(0,$.EN)(se),ie=n(9310);const le=e=>d.createElement(te.Z,{text:"loading.page"},d.createElement(h.Z,{id:e})),ce=(0,ne.ZP)((()=>n.e(370).then(n.bind(n,56370))),{fallback:le("loading.page.notfound")});class de extends d.Component{constructor(e){super(e),this.refreshListener=this.refreshListener.bind(this);const t=new Map;D.Z.getImmediateRoutes(!1).forEach((e=>{t.set(e.name,(0,ne.ZP)((()=>n(66235)(`./${e.file}`)),{fallback:le(e.name)}))})),this.state={loading:!!new URLSearchParams(window.location.search).get("state"),routes:D.Z.getImmediateRoutes(!1),components:t}}refreshListener(e){this.setState({routes:e})}async componentDidMount(){D.Z.on("refreshAll",this.refreshListener),this.props.history.listen((e=>{this.listener(e.pathname)})),this.listener(this.props.location.pathname);const e=new URLSearchParams(window.location.search),t=e.get("state");if(!t)return void this.setState({loading:!1});window.history.replaceState(null,document.title,window.location.pathname);const n=JSON.parse(window.sessionStorage.getItem("oauth")??"{}")[t];if(!n)return this.setErrorAndEnd(new v.ZP(v.jK.LOGIN_BAD_OAUTH,{jsError:Error(`State(${t}) cannot be resolved to a provider.`)}));const r=e.get("code");if(!r)return this.setErrorAndEnd(new v.ZP(v.jK.LOGIN_BAD_OAUTH,{jsError:Error("Code not found.")}));this.props.history.replace(n.url);const o=await b.Z.login({type:re.P.OAuth,provider:n.provider,token:r});if(window.sessionStorage.removeItem("oauth"),o.code!==E.G.OK)return this.setErrorAndEnd(o.error);this.setState({loading:!1})}componentWillUnmount(){D.Z.removeListener("refreshAll",this.refreshListener)}setErrorAndEnd(e){B.Mq.oautherrors=[e],this.setState({loading:!1})}listener(e){const t=D.Z.getImmediateRoutes(!1);for(const n of t)if(n.category&&n.navbarLoose&&(0,_.JW)(e,n.route)){this.props.selectCategory(n.category);break}}render(){return this.state.loading?d.createElement(te.Z,{text:"loading.app"}):d.createElement(Q,null,d.createElement(ae,null,d.createElement("div",null,d.createElement($.rs,null,this.state.routes.map((e=>{if(e.loginless||this.props.loggedIn)return d.createElement($.AW,{exact:!e.loose,path:e.route,key:e.name,render:t=>{let n;return n=e.cachedAuth?this.state.components.get(e.name):oe.Z,this.context?.user||e.loginless?this.context?.serverInfo||e==B.$w.config?e.noContainer?d.createElement(d.Fragment,null,d.createElement(n,t)):d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(n,t)):d.createElement(m.Z,null,d.createElement(V.ZP,{error:new v.ZP(v.jK.APP_FAIL,{jsError:Error("Router has no server info in the general context")})})):d.createElement(m.Z,null,d.createElement(V.ZP,{error:new v.ZP(v.jK.APP_FAIL,{jsError:Error("Router has no user in the general context")})}))}})})),d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement($.AW,{key:"notfound"},this.props.loggedIn?d.createElement(ce,null):d.createElement(ie.default,{loggedOut:this.props.loggedOut})))))))}}de.contextType=T.f;var ue=(0,$.EN)(de);class me{}me.en="en";var he=me;class pe{constructor(e,t){this.locale=e,this.messages=t}}class fe{static getShortHandedLocale(e){return e.split("-")[0]}async loadTranslation(e){const t=await n(862)(`./${e}.json`);if(!t){let t=fe.getShortHandedLocale(e);if(t===e){if(t===fe.fallbackLocale)throw new Error("Invalid locale: "+e);t=fe.fallbackLocale}return await this.loadTranslation(t)}let r=null;try{r=new pe(e,t)}catch(t){throw Error(`Error loading localization for locale '${e}': ${JSON.stringify(t)}`)}return r}}fe.fallbackLocale=he.en;var ge=fe;class ve extends d.Component{constructor(e){super(e),this.state={}}componentDidMount(){document.title="TGS Webpanel v"+g,document.addEventListener("keydown",(e=>{"L"===e.key&&e.ctrlKey&&e.shiftKey&&(b.Z.logout(),b.Z.login(w.Z.default))}))}render(){return d.createElement(f.VK,{basename:window.publicPath?new URL(window.publicPath,window.location.href).pathname:Y.UG},d.createElement(Q,null,d.createElement(z,{category:this.state.passdownCat,loggedIn:this.props.loggedIn}),this.props.loading?d.createElement(m.Z,{className:"mt-5 mb-5"},d.createElement(te.Z,{text:"loading.app"})):d.createElement(d.Fragment,null,d.createElement(m.Z,{className:"mt-5"},d.createElement(u.Z,{variant:"warning",className:"d-block d-lg-none"},d.createElement(u.Z.Heading,null,d.createElement(h.Z,{id:"warning.screensize.header"})),d.createElement("hr",null),d.createElement(h.Z,{id:"warning.screensize"})),Array.from(this.context.errors.values()).map(((e,t)=>d.createElement(V.ZP,{error:e,key:t,onClose:()=>this.context.deleteError(e)})))),d.createElement(ue,{loggedIn:this.props.loggedIn,loggedOut:this.props.loggedOut,selectCategory:e=>{this.setState({passdownCat:{name:e,key:Math.random().toString()}})}})),this.props.loggedIn?d.createElement(ee.Z,null):null),d.createElement(J,null),d.createElement(H,null))}}ve.contextType=T.f;class Ee extends d.Component{constructor(e){super(e),this.translationFactory=void 0,this.finishLogin=this.finishLogin.bind(this),this.finishLogout=this.finishLogout.bind(this),this.updateContextUser=this.updateContextUser.bind(this),this.updateContextServer=this.updateContextServer.bind(this),this.deleteGeneralContextError=this.deleteGeneralContextError.bind(this),this.translationFactory=this.props.translationFactory??new ge,this.state={loggedIn:!1,loggedOut:!1,loading:!0,GeneralContextInfo:{errors:new Set,user:null,serverInfo:null,deleteError:this.deleteGeneralContextError}}}async updateContextUser(){const e=await y.Z.getCurrentUser();e.code===E.G.OK?this.setState((t=>({GeneralContextInfo:{errors:t.GeneralContextInfo.errors,user:e.payload,serverInfo:t.GeneralContextInfo.serverInfo,deleteError:t.GeneralContextInfo.deleteError}}))):e.error.code===v.jK.HTTP_ACCESS_DENIED?this.setState((e=>({GeneralContextInfo:{user:null,serverInfo:e.GeneralContextInfo.serverInfo,deleteError:e.GeneralContextInfo.deleteError,errors:e.GeneralContextInfo.errors}}))):(setTimeout((()=>{this.updateContextUser()}),5e3),this.setState((t=>{const n=new Set(t.GeneralContextInfo.errors);return n.add(e.error),{GeneralContextInfo:{errors:n,deleteError:t.GeneralContextInfo.deleteError,user:null,serverInfo:t.GeneralContextInfo.serverInfo}}})))}async updateContextServer(e){const t=await b.Z.getServerInfo();t.code===E.G.OK?this.setState((n=>{const r=new Set(n.GeneralContextInfo.errors);return e&&r.delete(e),{GeneralContextInfo:{errors:r,user:n.GeneralContextInfo.user,serverInfo:t.payload,deleteError:n.GeneralContextInfo.deleteError}}})):(setTimeout((()=>{this.updateContextServer(t.error)}),5e3),this.setState((n=>{const r=new Set(n.GeneralContextInfo.errors);return r.add(t.error),e&&r.delete(e),{GeneralContextInfo:{errors:r,deleteError:n.GeneralContextInfo.deleteError,user:n.GeneralContextInfo.user,serverInfo:null}}})))}deleteGeneralContextError(e){this.setState((t=>{const n=new Set(t.GeneralContextInfo.errors);return n.delete(e),{GeneralContextInfo:{deleteError:t.GeneralContextInfo.deleteError,user:t.GeneralContextInfo.user,serverInfo:t.GeneralContextInfo.serverInfo,errors:n}}}))}finishLogin(){console.log("Logging in"),this.updateContextUser().then((()=>this.setState({loggedIn:!0,loading:!1})))}finishLogout(){this.setState({loggedIn:!1,loggedOut:!0}),this.updateContextUser()}async componentDidMount(){Z.Z.on("loginSuccess",this.finishLogin),b.Z.on("logout",this.finishLogout),b.Z.on("purgeCache",this.updateContextServer),b.Z.on("purgeCache",this.updateContextUser),await this.loadTranslation();const e=await b.Z.initApi();await this.updateContextServer(),e&&await this.updateContextUser(),this.setState({loading:!1,loggedIn:e})}componentWillUnmount(){Z.Z.removeListener("loginSuccess",this.finishLogin),b.Z.removeListener("logout",this.finishLogout),b.Z.removeListener("purgeCache",this.updateContextServer),b.Z.removeListener("purgeCache",this.updateContextUser)}render(){return this.state.translationError?d.createElement("p",{className:"App-error"},this.state.translationError):this.state.translation?d.createElement(p.Z,{locale:this.state.translation.locale,messages:this.state.translation.messages,defaultLocale:"en"},d.createElement(T.f.Provider,{value:this.state.GeneralContextInfo},d.createElement(ve,{loading:this.state.loading,loggedIn:this.state.loggedIn,loggedOut:this.state.loggedOut}))):d.createElement(te.Z,null,"Loading translations...")}async loadTranslation(){console.time("LoadTranslations");try{const e=await this.translationFactory.loadTranslation(this.props.locale);this.setState({translation:e})}catch(e){return void this.setState({translationError:JSON.stringify(e)??"An unknown error occurred"})}console.timeEnd("LoadTranslations")}}const be=d.createElement(d.StrictMode,null,d.createElement(Ee,{locale:he.en}));var ye=n(78947),we=n(51417),Ze=n(66370),Ie=n(83183),Ce=n(68055),Ae=n(24075),xe=n(47810),ke=n(57026),Se=n(20702),je=n(8792),Le=n(9752),Ne=n(19500),Oe=n(51257),Pe=n(16688),$e=n(68099),Ue=n(82414),Ge=n(84176),Re=n(62679),Te=n(609),_e=n(7371),De=n(40098),Be=n(16995),Fe=n(95337),Me=n(59986),ze=n(5702),Ke=n(91883),He=n(59545),We=n(55554),Je=n(42619),Ve=n(40864),qe=n(27879),Ye=n(46357),Xe=n(27593);ye.vI.add(Ce.LE,He.NB,Se.RL,qe.IL,Xe.wO,Oe.vc,Ke.UO,De.r8,Be.Ps,Pe.J9,We.Cg,xe.Kb,Ve.X7,$e.YH,Le._3,Ie.yO,Ge.DD,_e.Iw,we.zhw,we.omb,Je.$,Ue.sq,Ze.Pd,Ne.ne,Re.gf,ke.lX,je.cC,Ye.FV,Ae.mh,Fe.wn,Me.xf,ze.aC,Te.by,A.Kl4,A.tAh,Te.by,A.I4f,A.eW2,A.Mzg,A.olY,A.x58,A.Jvx,A.gMD,A.cwv,A.eHv,A.Yjj,A.acZ,Ie.yO,A.cf$,A.l9D),o.Z.loadconfig(),s.Z.init(),window.loadedChannelFromWebpack&&"DEV"!==Y.IK&&alert("Warning: channel.json was served from bundled files instead of TGS, the webpanel is running from the local version instead of the github update repo.\nPlease report this to your server host.\nIf you are the server host, please report this to alexkar598#2712 on discord\n\nWebpanel version: "+Y.q4);try{window.localStorage.removeItem("username"),window.sessionStorage.removeItem("username"),window.localStorage.removeItem("password"),window.sessionStorage.removeItem("password")}catch{}function Qe(){r.render(be,document.getElementById("root"))}window.addEventListener("DOMContentLoaded",Qe),"interactive"!==document.readyState&&"complete"!==document.readyState||Qe()},70601:function(e,t,n){"use strict";var r=n(12527),o=n(50452),s=n(1320);class a extends r.TypedEmitter{constructor(){super(),this.refreshing=!1,window.rtcontroller=this,this.refreshRoutes=this.refreshRoutes.bind(this),o.Z.addHook(this.refreshRoutes),this.refreshRoutes().catch(console.error),console.time("Category mapping");const e=new Map;for(const[t,n]of Object.entries(s.XT))n.routes=[],e.set(n.name,n),s.Nz[t]=n;for(const t of Object.values(s.$w)){if(!t.category)continue;const n=e.get(t.category);if(n){if(n.routes.push(t),t.catleader){if(n.leader){console.error("Category has two leaders",n.leader,t);continue}n.leader=t}}else console.error("Route has invalid category",t)}console.log("Categories mapped",e),console.timeEnd("Category mapping")}async refreshRoutes(){if(this.refreshing)return void console.log("Already refreshing");this.refreshing=!0;const e=[],t=this.getImmediateRoutes(!1);for(const n of t)n.cachedAuth=void 0,n.isAuthorized?e.push(n.isAuthorized().then((e=>{n.cachedAuth=e}))):n.cachedAuth=!0;await Promise.all(e),this.emit("refresh",this.getImmediateRoutes(!0));const n=this.getImmediateRoutes(!1);return this.emit("refreshAll",n),this.refreshing=!1,console.log("Routes refreshed",n),await this.getRoutes()}wait4refresh(){return new Promise((e=>{this.refreshing?this.on("refresh",(()=>{e()})):e()}))}async getRoutes(e=!0){return await this.wait4refresh(),this.getImmediateRoutes(e)}getImmediateRoutes(e=!0){const t=[];for(const n of Object.values(s.$w))n.isAuthorized&&!n.cachedAuth&&e||t.push(n);return t}}t.Z=new a},16964:function(e,t,n){"use strict";n.d(t,{$A:function(){return p},$V:function(){return m},D0:function(){return d},DB:function(){return u},H5:function(){return v},JW:function(){return a},JY:function(){return f},Kp:function(){return g},LR:function(){return o},N3:function(){return c},Zg:function(){return i},ko:function(){return s},mg:function(){return h}});var r=n(51068);function o(e,t){const n=document.createElement("a");n.setAttribute("href","data:text/plain;charset=utf-8,"+encodeURIComponent(t)),n.setAttribute("download",e),n.style.display="none",document.body.appendChild(n),n.click(),document.body.removeChild(n)}function s(e,t,n,r){return e.replace(new RegExp(t.replace(/([/,!\\^${}[\]().*+?|<>\-&])/g,"\\$&"),r?"gi":"g"),n.replace(/\$/g,"$$$$"))}function a(e,t,n=!1){return"/"===e.slice(-1)&&(e=e.slice(0,-1)),"/"===t.slice(-1)&&(t=t.slice(0,-1)),(0,r.Bo)(t,void 0,{end:n}).test(e)}function i(e){return e.permissionSet??e.group?.permissionSet}function l(e,t){return!!(t&e)}function c(e,t){return l(e.administrationRights,t)}function d(e,t){return l(e.instanceManagerRights,t)}function u(e,t){return l(e.engineRights,t)}function m(e,t){return l(e.chatBotRights,t)}function h(e,t){return l(e.dreamDaemonRights,t)}function p(e,t){return l(e.dreamMakerRights,t)}function f(e,t){return l(e.instancePermissionSetRights,t)}function g(e,t){return l(e.repositoryRights,t)}function v(e,t){return l(e.configurationRights,t)}},1320:function(e,t,n){"use strict";n.d(t,{$w:function(){return d},Mq:function(){return h},Nz:function(){return m},XT:function(){return u}});var r=n(48509),o=n(53803),s=n(16942),a=n(42522),i=n(16964);function l(e){return async()=>{if(!a.Z.hasToken())return!1;const t=await s.Z.getCurrentUser();return t.code==o.G.OK&&!!((0,i.Zg)(t.payload).administrationRights&e)}}function c(e){return async()=>{if(!a.Z.hasToken())return!1;const t=await s.Z.getCurrentUser();return t.code==o.G.OK&&!!((0,i.Zg)(t.payload).instanceManagerRights&e)}}const d={home:{name:"routes.home",route:"/",file:"Home",loose:!1,navbarLoose:!1,visibleNavbar:!0,homeIcon:void 0,category:"home",catleader:!0},instancecreate:{name:"routes.instancecreate",route:"/instances/create",file:"Instance/Create",loose:!1,navbarLoose:!1,isAuthorized:c(r.c2.Create),visibleNavbar:!1,category:"instance",catleader:!1},instancelist:{name:"routes.instancelist",route:"/instances/",file:"Instance/List",loose:!1,navbarLoose:!0,isAuthorized:c(r.c2.List|r.c2.Read),visibleNavbar:!0,homeIcon:"hdd",category:"instance",catleader:!0},instanceedit:{name:"routes.instanceedit",route:"/instances/edit/:id(\\d+)/:tab?/",file:"Instance/InstanceEdit",get link(){return void 0!==h.selectedinstanceid?`/instances/edit/${h.selectedinstanceid}/${void 0!==h.selectedinstanceedittab?`${h.selectedinstanceedittab}/`:""}`:d.instancelist.link??d.instancelist.route},loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"instance"},instancejobs:{name:"routes.instancejobs",route:"/instances/jobs/",file:"Instance/Jobs",loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"instance"},userlist:{name:"routes.usermanager",route:"/users/",file:"User/List",loose:!1,navbarLoose:!0,visibleNavbar:!0,homeIcon:"user",category:"user",catleader:!0},useredit:{name:"routes.useredit",route:"/users/edit/user/:id(\\d+)/:tab?/",get link(){return void 0!==h.selecteduserid?`/users/edit/user/${h.selecteduserid}/${void 0!==h.selectedusertab?`${h.selectedusertab}/`:""}`:d.userlist.link??d.userlist.route},file:"User/Edit",loose:!0,navbarLoose:!0,visibleNavbar:!0,homeIcon:void 0,category:"user"},usercreate:{name:"routes.usercreate",route:"/users/create/",link:"/users/create/",file:"User/Create",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.WriteUsers),visibleNavbar:!0,homeIcon:void 0,category:"user"},admin:{name:"routes.admin",route:"/admin/",file:"Administration",loose:!1,navbarLoose:!0,isAuthorized:l(r.oj.ChangeVersion|r.oj.DownloadLogs|r.oj.UploadVersion),visibleNavbar:!0,homeIcon:"tools",category:"admin",catleader:!0},admin_update:{name:"routes.admin.update",route:"/admin/update/:all?/",file:"Admin/Update",link:"/admin/update/",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.ChangeVersion|r.oj.UploadVersion),visibleNavbar:!0,homeIcon:void 0,category:"admin"},admin_logs:{name:"routes.admin.logs",route:"/admin/logs/:name?/",link:"/admin/logs/",file:"Admin/Logs",loose:!1,navbarLoose:!0,isAuthorized:l(r.oj.DownloadLogs),visibleNavbar:!0,homeIcon:void 0,category:"admin",noContainer:!0},passwd:{name:"routes.passwd",route:"/users/passwd/:id(\\d+)?/",link:"/users/passwd/",file:"ChangePassword",loose:!0,navbarLoose:!0,isAuthorized:l(r.oj.EditOwnPassword),visibleNavbar:!1,homeIcon:"key"},config:{name:"routes.config",route:"/config/",file:"Configuration",loose:!0,navbarLoose:!0,loginless:!0,visibleNavbar:!1,homeIcon:"cogs"},setup:{name:"routes.setup",route:"/setup/",file:"Setup",loose:!0,navbarLoose:!0,loginless:!0,visibleNavbar:!1},oAuth:{name:"routes.oauth",route:"/oauth/:provider?/",file:"Login",loose:!0,navbarLoose:!1,loginless:!0,visibleNavbar:!1},info:{name:"routes.info",route:"/info",file:"Info",loose:!1,navbarLoose:!1,loginless:!0,visibleNavbar:!0,homeIcon:"info-circle",category:void 0,catleader:!1}},u={home:{name:"home"},instance:{name:"instance"},user:{name:"user"},admin:{name:"admin"}},m={},h={selecteduserid:void 0,selectedusertab:void 0,selectedinstanceid:void 0,selectedinstanceedittab:void 0,instancelistpage:void 0,loglistpage:void 0,byondlistpage:void 0,userlistpage:void 0,jobhistorypage:new Map,oautherrors:[]}},39087:function(e,t,n){"use strict";var r=n(94015),o=n.n(r),s=n(23645),a=n.n(s),i=n(61667),l=n.n(i),c=n(31100),d=a()(o()),u=l()(c);d.push([e.id,".App {\n background-size: 50%;\n background: #1e1e1e url("+u+") no-repeat center;\n position: absolute;\n width: 100%;\n top: 0;\n bottom: 0;\n display: grid;\n}\n\n.App-error {\n color: red;\n font-size: 150%;\n margin: auto;\n}\n\n.App-main {\n display: grid;\n}\n\n.Root {\n overflow: hidden;\n display: grid;\n grid-template-rows: 9% auto;\n}\n\n.Root-login {\n display: grid;\n}\n","",{version:3,sources:["webpack://./src/App.css"],names:[],mappings:"AAAA;IACI,oBAAoB;IACpB,4EAAkD;IAClD,kBAAkB;IAClB,WAAW;IACX,MAAM;IACN,SAAS;IACT,aAAa;AACjB;;AAEA;IACI,UAAU;IACV,eAAe;IACf,YAAY;AAChB;;AAEA;IACI,aAAa;AACjB;;AAEA;IACI,gBAAgB;IAChB,aAAa;IACb,2BAA2B;AAC/B;;AAEA;IACI,aAAa;AACjB",sourcesContent:[".App {\n background-size: 50%;\n background: #1e1e1e url(logo.svg) no-repeat center;\n position: absolute;\n width: 100%;\n top: 0;\n bottom: 0;\n display: grid;\n}\n\n.App-error {\n color: red;\n font-size: 150%;\n margin: auto;\n}\n\n.App-main {\n display: grid;\n}\n\n.Root {\n overflow: hidden;\n display: grid;\n grid-template-rows: 9% auto;\n}\n\n.Root-login {\n display: grid;\n}\n"],sourceRoot:""}]),t.Z=d},58007:function(e,t,n){"use strict";var r=n(94015),o=n.n(r),s=n(23645),a=n.n(s)()(o());a.push([e.id,".tgs-update-notification {\n color: #66ff07;\n}\n","",{version:3,sources:["webpack://./src/components/AppNavbar.css"],names:[],mappings:"AAAA;IACI,cAAc;AAClB",sourcesContent:[".tgs-update-notification {\n color: #66ff07;\n}\n"],sourceRoot:""}]),t.Z=a},66235:function(e,t,n){var r={"./Admin/Logs":[43408,171,408],"./Admin/Logs.tsx":[43408,171,408],"./Admin/Update":[80732,171,578,6,724,732],"./Admin/Update.tsx":[80732,171,578,6,724,732],"./Administration":[29363,171,363],"./Administration.tsx":[29363,171,363],"./ChangePassword":[61304,799],"./ChangePassword.tsx":[61304,799],"./Configuration":[67671,671],"./Configuration.tsx":[67671,671],"./Home":[59638,638],"./Home.tsx":[59638,638],"./Info":[41051,171,51],"./Info.tsx":[41051,171,51],"./Instance/Create":[38747,899,856,637,756,611,578,6,747],"./Instance/Create.tsx":[38747,899,856,637,756,611,578,6,747],"./Instance/Edit/ChatBots":[90740,767,171,740,318],"./Instance/Edit/ChatBots.tsx":[90740,767,171,740,318],"./Instance/Edit/Config":[62685,171,685],"./Instance/Edit/Config.tsx":[62685,171,685],"./Instance/Edit/Deployment":[44298,899,856,171,578,240,67,356],"./Instance/Edit/Deployment.tsx":[44298,899,856,171,578,240,67,356],"./Instance/Edit/Engine":[32240,899,171,578,240,657],"./Instance/Edit/Engine.tsx":[32240,899,171,578,240,657],"./Instance/Edit/Files":[20926,637,171,926,608],"./Instance/Edit/Files.tsx":[20926,637,171,926,608],"./Instance/Edit/InstancePermissions":[87345,803,171,345,246],"./Instance/Edit/InstancePermissions.tsx":[87345,803,171,345,246],"./Instance/Edit/JobHistory":[25921,171,921],"./Instance/Edit/JobHistory.tsx":[25921,171,921],"./Instance/Edit/Repository":[18264,856,611,171,578,757,264,233],"./Instance/Edit/Repository.tsx":[18264,856,611,171,578,757,264,233],"./Instance/Edit/Server":[86046,899,756,171,578,240,67,792],"./Instance/Edit/Server.tsx":[86046,899,756,171,578,240,67,792],"./Instance/InstanceEdit":[9182,899,856,637,756,611,803,767,171,578,757,165,240,67,264,740,926,345,182],"./Instance/InstanceEdit.tsx":[9182,899,856,637,756,611,803,767,171,578,757,165,240,67,264,740,926,345,182],"./Instance/Jobs":[41818,818],"./Instance/Jobs.tsx":[41818,818],"./Instance/List":[70670,171,670],"./Instance/List.tsx":[70670,171,670],"./Login":[9310],"./Login.tsx":[9310],"./Setup":[12757,666],"./Setup.tsx":[12757,666],"./User/Create":[14898,898],"./User/Create.tsx":[14898,898],"./User/Edit":[11404,803,171,404],"./User/Edit.tsx":[11404,803,171,404],"./User/List":[8746,171,746],"./User/List.tsx":[8746,171,746]};function o(e){if(!n.o(r,e))return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=r[e],o=t[0];return Promise.all(t.slice(1).map(n.e)).then((function(){return n(o)}))}o.keys=function(){return Object.keys(r)},o.id=66235,e.exports=o},862:function(e,t,n){var r={"./en.json":[2422,422]};function o(e){if(!n.o(r,e))return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=r[e],o=t[0];return n.e(t[1]).then((function(){return n.t(o,19)}))}o.keys=function(){return Object.keys(r)},o.id=862,e.exports=o},31100:function(e){"use strict";e.exports="https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/835dd74f1c08a7f91a60.svg"}},a={};function i(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={id:e,exports:{}};return s[e].call(n.exports,n,n.exports,i),n.exports}i.m=s,e=[],i.O=function(t,n,r,o){if(!n){var s=1/0;for(d=0;d=o)&&Object.keys(i.O).every((function(e){return i.O[e](n[l])}))?n.splice(l--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},i.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);i.r(o);var s={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){s[t]=function(){return e[t]}}));return s.default=function(){return e},i.d(o,s),o},i.d=function(e,t){for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.f={},i.e=function(e){return Promise.all(Object.keys(i.f).reduce((function(t,n){return i.f[n](e,t),t}),[]))},i.u=function(e){return e+"."+{6:"8be2a144a2793570315b",51:"018c826056e10797488e",67:"d56fce4f5250e06063bf",165:"ba83e5a7e5ce7342f32c",171:"91936e72280a3d6afb03",182:"863e8e49ed9d42931724",233:"bca5af88bf85dd9388cf",240:"9d4d9c96f62ce48cce5f",246:"67f9678139a3c824ae5c",264:"6bc80619e126b48208c1",318:"262f7ca0e51a961f2197",345:"463f4581d9005d5a1f8f",356:"8162c10f9d74465626fb",363:"d935e8c7446b49ba6955",370:"d23700bdb9e4969823c6",404:"a8f6c9632768de417c6c",408:"67a24726289357584bda",422:"de987a83a6a343eed949",578:"b30e90acaefb17f3a915",608:"cdba29b0c970b4473f8d",611:"9c4e031ea3c5676ddc02",637:"67f97d250d5cc7c88b41",638:"22ae8c317d679c0fbe03",657:"ebfcf366f770219cda87",666:"466319a2ec0bfe75619b",670:"f23545dfb1fa34187665",671:"d70d3c2a0ea3e94cdaa3",685:"d842923a8b4fb2925914",724:"37608abcbb4a745b53ee",732:"26d1b1972e8409d84e2b",740:"ff042fc269d749115529",746:"24e79402e2a97d9d230f",747:"a7a7de444f0f31df471a",756:"3ea7efa9992f327c4f5e",757:"cd59822a9cafc5472de2",767:"2226708e286478875491",792:"d0502c03902c2b81a3bc",799:"5f592f7ea70157f79ee3",803:"e84dc0cafa5ef825835a",818:"de10c00448eedd21537d",856:"4c7ce7828aeda2beaebd",898:"506fa6125431a4f5701d",899:"40a4d85a43cf77edbe66",921:"d5768a750d900f116d8c",926:"9721f261bd7f5a7449df"}[e]+".bundle.js"},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="tgstation-server-control-panel:",i.l=function(e,t,n,s){if(r[e])r[e].push(t);else{var a,l;if(void 0!==n)for(var c=document.getElementsByTagName("script"),d=0;d 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var getProto = Object.getPrototypeOf ? function(obj) { return Object.getPrototypeOf(obj); } : function(obj) { return obj.__proto__; };\nvar leafPrototypes;\n// create a fake namespace object\n// mode & 1: value is a module id, require it\n// mode & 2: merge all properties of value into the ns\n// mode & 4: return value when already ns object\n// mode & 16: return value when it's Promise-like\n// mode & 8|1: behave like require\n__webpack_require__.t = function(value, mode) {\n\tif(mode & 1) value = this(value);\n\tif(mode & 8) return value;\n\tif(typeof value === 'object' && value) {\n\t\tif((mode & 4) && value.__esModule) return value;\n\t\tif((mode & 16) && typeof value.then === 'function') return value;\n\t}\n\tvar ns = Object.create(null);\n\t__webpack_require__.r(ns);\n\tvar def = {};\n\tleafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];\n\tfor(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {\n\t\tObject.getOwnPropertyNames(current).forEach(function(key) { def[key] = function() { return value[key]; }; });\n\t}\n\tdef['default'] = function() { return value; };\n\t__webpack_require__.d(ns, def);\n\treturn ns;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"tgstation-server-control-panel:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = function(url, done, key, chunkId) {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 15;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = function(prev, event) {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach(function(fn) { return fn(event); });\n\t\tif(prev) return prev(event);\n\t};\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 15000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","import React, { ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\nimport GenericAlert from \"./GenericAlert\";\n\ninterface IProps extends RouteComponentProps {}\n\ninterface IState {\n auth: boolean;\n}\n\nclass AccessDenied extends React.Component {\n public render(): ReactNode {\n const goBack = () => {\n this.props.history.goBack();\n };\n return (\n \n \n \n );\n }\n}\n\nexport default withRouter(AccessDenied);\n","import ClickToSelect from \"@mapbox/react-click-to-select\";\nimport React, { Component, ReactNode } from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport Button from \"react-bootstrap/Button\";\nimport Modal from \"react-bootstrap/Modal\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport InternalError, {\n DescType,\n ErrorCode\n} from \"../../ApiClient/models/InternalComms/InternalError\";\nimport { API_VERSION, MODE, VERSION } from \"../../definitions/constants\";\n\ninterface IProps {\n error: InternalError | undefined;\n onClose?: () => void;\n}\n\ninterface IState {\n popup: boolean;\n}\n\nclass ErrorAlert extends Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {\n popup: false\n };\n }\n public render(): ReactNode {\n if (!this.props.error) {\n return \"\";\n }\n\n const handleClose = () => this.setState({ popup: false });\n const handleOpen = () => this.setState({ popup: true });\n\n return (\n \n \n
\n\n \n\n \n \n \n \n \n \n \n {this.props.error.desc?.type === DescType.LOCALE ? (\n \n ) : this.props.error.desc?.desc ? (\n this.props.error.desc.desc\n ) : (\n \"\"\n )}\n
\n \n \n {`Webpanel Version: ${VERSION}\nWebpanel Mode: ${MODE}\nAPI Version: ${API_VERSION}\n\nError Code: ${this.props.error.code}\nError Description: ${this.props.error.desc ? this.props.error.desc.desc : \"No description\"}\n\nAdditional Information:\n${this.props.error.extendedInfo}`.replace(/\\\\/g, \"\\\\\\\\\")}\n \n \n
\n \n \n \n \n \n \n
\n \n );\n }\n}\n\nexport default ErrorAlert;\n\nexport type ErrorState = [\n Array | undefined>,\n React.Dispatch | undefined>>>\n];\n\nfunction addError([, setErrors]: ErrorState, error: InternalError): void {\n setErrors(prevState => {\n const errors = Array.from(prevState);\n errors.push(error);\n return errors;\n });\n}\n\nfunction displayErrors([errors, setErrors]: ErrorState): Array {\n return errors.map((err, index) => {\n if (!err) return;\n return (\n \n setErrors(prev => {\n const newarr = Array.from(prev);\n newarr[index] = undefined;\n return newarr;\n })\n }\n />\n );\n });\n}\n\nexport { displayErrors, addError };\n","import React from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport { FormattedMessage } from \"react-intl\";\n\ninterface IProps {\n title: string;\n body?: string;\n children?: JSX.Element;\n}\n\nexport default function GenericAlert(props: IProps): JSX.Element {\n return (\n \n \n {props.body ? (\n \n
\n \n
\n ) : props.children ? (\n \n
\n {props.children}\n
\n ) : null}\n
\n );\n}\n","import ClickToSelect from \"@mapbox/react-click-to-select\";\nimport React, { useState } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport Modal from \"react-bootstrap/Modal\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport { ErrorCode as TGSErrorCode } from \"../../ApiClient/generatedcode/generated\";\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\n\ninterface IProps {\n job: TGSJobResponse;\n}\n\nexport default function JobError(props: IProps): JSX.Element {\n const [open, setOpen] = useState(false);\n return (\n <>\n setOpen(true)}\n size=\"sm\">\n \n \n\n setOpen(false)} size=\"lg\">\n \n \n \n \n \n \n :{\" \"}\n {props.job.errorCode !== undefined && props.job.errorCode !== null\n ? TGSErrorCode[props.job.errorCode]\n : \"NoCode\"}\n
\n \n \n {props.job.exceptionDetails}\n \n \n
\n \n \n \n
\n \n );\n}\n","import { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport OverlayTrigger from \"react-bootstrap/OverlayTrigger\";\nimport ProgressBar from \"react-bootstrap/ProgressBar\";\nimport Toast from \"react-bootstrap/Toast\";\nimport ToastBody from \"react-bootstrap/ToastBody\";\nimport ToastHeader from \"react-bootstrap/ToastHeader\";\nimport Tooltip from \"react-bootstrap/Tooltip\";\nimport { FormattedMessage, FormattedRelativeTime } from \"react-intl\";\n\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\nimport JobError from \"./JobError\";\n\ninterface IState {}\ninterface IProps {\n job: TGSJobResponse;\n width?: string;\n onClose?: (job: TGSJobResponse) => void;\n onCancel: (job: TGSJobResponse) => void;\n}\n\nexport default class JobCard extends React.Component {\n public render(): ReactNode {\n const job = this.props.job;\n const createddate = new Date(job.startedAt);\n const createddiff = (createddate.getTime() - Date.now()) / 1000;\n const stoppeddate = new Date(job.stoppedAt ?? 0);\n const stoppeddiff = (stoppeddate.getTime() - Date.now()) / 1000;\n const variant =\n job.errorCode !== undefined || job.exceptionDetails !== undefined\n ? \"danger\"\n : job.cancelled\n ? \"warning\"\n : job.stoppedAt\n ? \"success\"\n : \"info\";\n\n return (\n {\n if (this.props.onClose) this.props.onClose(job);\n }}>\n \n #{job.id}: {job.description}\n \n \n {/*STAGE*/}\n {job.stage ?
â–¶{job.stage}
: null}\n {/*STARTED AT*/}\n \n \n {createddate.toLocaleString()}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }>\n \n \n )}\n \n
\n\n {/*CREATED BY*/}\n \n \n \n {job.startedBy.id}\n \n }>\n {({ ref, ...triggerHandler }) => (\n } {...triggerHandler}>\n {job.startedBy.name}\n \n )}\n \n
\n
\n {/*STOPPED AT*/}\n {job.stoppedAt ? (\n \n \n \n {createddate.toLocaleString()}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }>\n \n \n )}\n \n
\n
\n ) : (\n \"\"\n )}\n {job.cancelledBy ? (\n \n \n \n \n {job.startedBy.id}\n \n }>\n {({ ref, ...triggerHandler }) => (\n }\n {...triggerHandler}>\n {job.cancelledBy!.name}\n \n )}\n \n
\n
\n ) : (\n \"\"\n )}\n\n {/*ERROR*/}\n {job.errorCode !== undefined || job.exceptionDetails !== undefined ? (\n \n ) : (\n \"\"\n )}\n
\n \n {job.canCancel && !job.stoppedAt ? (\n this.props.onCancel(job)}>\n \n \n ) : null}\n
\n
\n \n );\n }\n}\n","import { faTimes } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ReactNode } from \"react\";\nimport { Button, Card } from \"react-bootstrap\";\nimport { OverlayInjectedProps } from \"react-bootstrap/Overlay\";\nimport OverlayTrigger from \"react-bootstrap/OverlayTrigger\";\nimport Tooltip from \"react-bootstrap/Tooltip\";\nimport { FormattedMessage } from \"react-intl\";\nimport { Rnd } from \"react-rnd\";\n\nimport type { InstanceResponse } from \"../../ApiClient/generatedcode/generated\";\nimport { TGSJobResponse } from \"../../ApiClient/JobsClient\";\nimport InternalError, { ErrorCode } from \"../../ApiClient/models/InternalComms/InternalError\";\nimport configOptions, { jobsWidgetOptions } from \"../../ApiClient/util/config\";\nimport JobsController from \"../../ApiClient/util/JobsController\";\nimport ErrorAlert from \"./ErrorAlert\";\nimport JobCard from \"./JobCard\";\nimport Loading from \"./Loading\";\n\ninterface IProps {\n width?: string;\n widget: boolean;\n}\n\ninterface IState {\n jobs: Map>;\n errors: InternalError[];\n nextRetrySeconds: number | null;\n ownerrors: Array | undefined>;\n loading: boolean;\n instances: Map;\n}\n\nexport default class JobsList extends React.Component {\n public static defaultProps = {\n widget: true\n };\n\n private widgetRef = React.createRef();\n\n public constructor(props: IProps) {\n super(props);\n\n this.handleUpdate = this.handleUpdate.bind(this);\n this.onCancel = this.onCancel.bind(this);\n this.onClose = this.onClose.bind(this);\n\n this.state = {\n jobs: JobsController.jobsByInstance,\n errors: [],\n nextRetrySeconds: null,\n ownerrors: [],\n loading: true,\n instances: new Map()\n };\n }\n\n private addError(error: InternalError): void {\n this.setState(prevState => {\n const ownerrors = Array.from(prevState.ownerrors);\n ownerrors.push(error);\n if (this.widgetRef.current) {\n this.widgetRef.current.scrollTop = 0;\n }\n return {\n ownerrors\n };\n });\n }\n\n public componentDidMount(): void {\n JobsController.on(\"jobsLoaded\", this.handleUpdate);\n this.handleUpdate();\n }\n\n public componentWillUnmount(): void {\n JobsController.removeListener(\"jobsLoaded\", this.handleUpdate);\n }\n\n private currentTimeout?: NodeJS.Timeout | null;\n\n public handleUpdate(): void {\n if (this.currentTimeout) {\n clearTimeout(this.currentTimeout);\n this.currentTimeout = null;\n }\n\n let nextRetrySeconds;\n if (JobsController.nextRetry) {\n if (JobsController.nextRetry.getSeconds() > new Date().getSeconds()) {\n nextRetrySeconds = JobsController.nextRetry.getSeconds() - new Date().getSeconds();\n } else {\n nextRetrySeconds = 0;\n }\n this.currentTimeout = setTimeout(() => this.handleUpdate(), 1000);\n } else {\n nextRetrySeconds = null;\n }\n\n this.setState({\n jobs: JobsController.jobsByInstance,\n errors: JobsController.errors,\n nextRetrySeconds,\n loading: false,\n instances: JobsController.accessibleInstances\n });\n }\n\n private async onCancel(job: TGSJobResponse) {\n const status = await JobsController.cancelJob(job.id, error => this.addError(error));\n\n if (!status) {\n return;\n }\n JobsController.fastmode = 5;\n }\n\n private onClose(job: TGSJobResponse) {\n JobsController.clearJob(job.id);\n }\n\n public render(): ReactNode {\n if (!this.props.widget) return this.nested();\n\n let totalJobs = 0;\n for (const job of this.state.jobs.values()) {\n totalJobs += job.size;\n }\n\n let display: boolean;\n if (configOptions.jobswidgetdisplay.value === jobsWidgetOptions.NEVER) {\n display = false;\n } else if (configOptions.jobswidgetdisplay.value === jobsWidgetOptions.ALWAYS) {\n display = true;\n } else {\n display = totalJobs > 0 || this.state.errors.length > 0;\n }\n\n return (\n \n \n
\n
\n \n
\n {this.nested()}\n
\n \n \n );\n }\n\n private nested(): ReactNode {\n return (\n
\n {this.state.loading ? : \"\"}\n {this.state.ownerrors.map((err, index) => {\n if (!err) return;\n return (\n \n this.setState(prev => {\n const newarr = Array.from(prev.ownerrors);\n newarr[index] = undefined;\n return {\n ownerrors: newarr\n };\n })\n }\n />\n );\n })}\n {this.state.errors.length > 0 ? (\n \n {this.state.errors.map((error, index) => {\n return (\n \n \n
\n );\n })}\n \n {this.state.nextRetrySeconds === 0 ? (\n \n ) : this.state.nextRetrySeconds != null ? (\n
\n ) : (\n \n )}\n \n \n ) : null}\n {Array.from(this.state.jobs)\n .sort((a, b) => a[0] - b[0])\n .map(([instanceid, jobMap]) => {\n const renderTooltip = (instanceid: number) => {\n return (props: OverlayInjectedProps) => (\n \n {instanceid}\n \n );\n };\n\n let xFinishedEnabled = false;\n jobMap.forEach(job => {\n if (job.stoppedAt) xFinishedEnabled = true;\n });\n\n const instanceHeaderStyle = xFinishedEnabled\n ? { marginTop: \"5px\", marginLeft: \"20px\" }\n : undefined;\n\n return (\n \n
\n
\n
\n \n \n {this.state.instances.get(instanceid)?.name ??\n \"Unknown\"}{\" \"}\n (\n \n )\n \n \n
\n
\n {xFinishedEnabled ? (\n
\n (\n \n \n \n )}>\n \n jobMap.forEach(job => {\n if (job.stoppedAt)\n JobsController.clearJob(job.id);\n })\n }\n className=\"nowrap\">\n \n \n \n
\n ) : (\n \n )}\n
\n {Array.from(jobMap, ([, job]) => job)\n .sort((a, b) => b.id - a.id)\n .map(job => (\n \n ))}\n
\n );\n })}\n \n );\n }\n}\n","import React, { ReactNode } from \"react\";\nimport Spinner, { SpinnerProps } from \"react-bootstrap/Spinner\";\nimport { FormattedMessage } from \"react-intl\";\nimport CSSTransition from \"react-transition-group/CSSTransition\";\nimport TransitionGroup from \"react-transition-group/TransitionGroup\";\n\ntype IProps = SpinnerProps & {\n animation: \"border\" | \"grow\";\n center: boolean;\n width: number;\n widthUnit: string;\n className?: string;\n text?: string;\n};\n\ninterface IState {}\n\nexport default class Loading extends React.Component {\n public static defaultProps = {\n animation: \"border\",\n width: \"50\",\n widthUnit: \"vmin\",\n center: true\n };\n public constructor(props: IProps) {\n super(props);\n }\n\n public render(): ReactNode {\n const {\n variant,\n animation,\n center,\n className,\n width,\n widthUnit,\n text,\n children,\n ...otherprops\n } = this.props;\n const styles: React.CSSProperties = {\n width: `${width}${widthUnit}`,\n height: `${width}${widthUnit}`\n } as React.CSSProperties;\n return (\n \n {\n node.addEventListener(\"transitionend\", done, false);\n }}>\n
\n \n {text ? : \"\"}\n {children}\n
\n \n
\n );\n }\n}\n","import { faInvision } from \"@fortawesome/free-brands-svg-icons\";\nimport { faDiscord } from \"@fortawesome/free-brands-svg-icons/faDiscord\";\nimport { faGithub } from \"@fortawesome/free-brands-svg-icons/faGithub\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React, { ChangeEvent, FormEvent, ReactNode } from \"react\";\nimport Button from \"react-bootstrap/Button\";\nimport Col from \"react-bootstrap/Col\";\nimport Card from \"react-bootstrap/esm/Card\";\nimport Form from \"react-bootstrap/Form\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps } from \"react-router\";\nimport { withRouter } from \"react-router-dom\";\n\nimport { OAuthProvider } from \"../../ApiClient/generatedcode/generated\";\nimport { CredentialsType } from \"../../ApiClient/models/ICredentials\";\nimport InternalError, { ErrorCode } from \"../../ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"../../ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"../../ApiClient/ServerClient\";\nimport CredentialsProvider from \"../../ApiClient/util/CredentialsProvider\";\nimport { GeneralContext, UnsafeGeneralContext } from \"../../contexts/GeneralContext\";\nimport { MODE } from \"../../definitions/constants\";\nimport KeycloakLogo from \"../../images/keycloak_icon_64px.png\";\nimport TGLogo from \"../../images/tglogo-white.svg\";\nimport { RouteData } from \"../../utils/routes\";\nimport ErrorAlert from \"../utils/ErrorAlert\";\nimport Loading from \"../utils/Loading\";\n\ninterface IProps extends RouteComponentProps {\n loggedOut: boolean;\n}\ninterface IState {\n busy: boolean;\n validated: boolean;\n username: string;\n password: string;\n errors: Array | undefined>;\n redirectSetup?: boolean;\n}\n\nexport type StoredOAuthData = { provider: OAuthProvider; url: string };\nexport type OAuthStateStorage = Record;\n\nclass Login extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: IProps) {\n super(props);\n this.submit = this.submit.bind(this);\n\n console.log(RouteData.oautherrors);\n\n this.state = {\n busy: false,\n validated: false,\n username: \"\",\n password: \"\",\n errors: Array.from(RouteData.oautherrors)\n };\n }\n\n public async componentDidMount(): Promise {\n const oauthState =\n window.sessionStorage.getItem(\"oauth\") ??\n CredentialsProvider.credentials?.type === CredentialsType.OAuth;\n if (!oauthState && (MODE === \"PROD\" || MODE === \"GITHUB\")) {\n // noinspection ES6MissingAwait\n await this.tryLoginDefault();\n }\n }\n\n private async tryLoginDefault(): Promise {\n if (this.props.loggedOut) {\n return;\n }\n\n const response = await ServerClient.login(CredentialsProvider.default);\n\n if (response.code === StatusCode.OK) {\n this.setState({\n redirectSetup: true\n });\n }\n }\n\n private addError(error: InternalError): void {\n this.setState(prevState => {\n const errors = Array.from(prevState.errors);\n errors.push(error);\n return {\n errors\n };\n });\n }\n\n public render(): ReactNode {\n const handleUsrInput = (event: ChangeEvent) =>\n this.setState({ username: event.target.value });\n const handlePwdInput = (event: ChangeEvent) =>\n this.setState({ password: event.target.value });\n\n if (this.state.busy || CredentialsProvider.hasToken()) {\n return ;\n }\n\n if (!this.context.serverInfo) {\n return ;\n }\n\n const providers: Record = {\n [OAuthProvider.GitHub]: ,\n [OAuthProvider.Discord]: (\n \n ),\n [OAuthProvider.TGForums]: \"tglogo\",\n [OAuthProvider.Keycloak]: (\n \"keycloaklogo\"\n ),\n [OAuthProvider.InvisionCommunity]: (\n \n )\n };\n\n const providersTheme: Record = {\n GitHub: \"#161b22\",\n Discord: \"#7289da\",\n TGForums: undefined,\n Keycloak: undefined,\n InvisionCommunity: undefined\n };\n\n return (\n \n {this.state.errors.map((err, index) => {\n if (!err) return;\n return (\n \n this.setState(prev => {\n const newarr = Array.from(prev.errors);\n newarr[index] = undefined;\n return {\n errors: newarr\n };\n })\n }\n />\n );\n })}\n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n {(this.context.serverInfo?.oAuthProviderInfos?.Discord ||\n this.context.serverInfo?.oAuthProviderInfos?.GitHub ||\n this.context.serverInfo?.oAuthProviderInfos?.Keycloak ||\n this.context.serverInfo?.oAuthProviderInfos?.TGForums ||\n this.context.serverInfo?.oAuthProviderInfos?.InvisionCommunity) && (\n <>\n
\n \n \n \n \n {Object.keys(this.context.serverInfo.oAuthProviderInfos ?? {}).map(\n provider => {\n const ptheme = providersTheme[provider as OAuthProvider];\n return (\n \n this.startOAuth(provider as OAuthProvider)\n }>\n {providers[provider as OAuthProvider]}\n \n \n \n \n );\n }\n )}\n \n \n )}\n
\n \n );\n }\n\n private async startOAuth(provider: OAuthProvider): Promise {\n if (!this.context.serverInfo) {\n this.addError(\n new InternalError(ErrorCode.APP_FAIL, {\n jsError: Error(\"serverInfo is null in startOAuth\")\n })\n );\n return;\n }\n\n const stateArray = new Uint8Array(10);\n window.crypto.getRandomValues(stateArray);\n const state = Array.from(stateArray, dec => dec.toString(16).padStart(2, \"0\")).join(\"\");\n\n let url: string | undefined = undefined;\n\n const e = encodeURIComponent;\n\n switch (provider) {\n case OAuthProvider.Discord: {\n url = `https://discord.com/api/oauth2/authorize?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.Discord.clientId\n )}&scope=identify&state=${e(state)}`;\n const discordRedirect = this.context.serverInfo.oAuthProviderInfos.Discord\n .redirectUri;\n if (discordRedirect) {\n url = `${url}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.Discord.redirectUri\n )}`;\n }\n\n break;\n }\n case OAuthProvider.GitHub: {\n url = `https://github.com/login/oauth/authorize?client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.GitHub.clientId\n )}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.GitHub.redirectUri\n )}&state=${e(state)}&allow_signup=false`;\n break;\n }\n case OAuthProvider.Keycloak: {\n url = `${this.context.serverInfo.oAuthProviderInfos.Keycloak\n .serverUrl!}/protocol/openid-connect/auth?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.Keycloak.clientId\n )}&scope=openid&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.Keycloak.redirectUri\n )}`;\n break;\n }\n case OAuthProvider.TGForums: {\n url = `https://tgstation13.org/phpBB/app.php/tgapi/oauth/auth?scope=user&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.TGForums.clientId\n )}&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.TGForums.redirectUri\n )}`;\n break;\n }\n case OAuthProvider.InvisionCommunity: {\n url = `${this.context.serverInfo.oAuthProviderInfos.InvisionCommunity\n .serverUrl!}/oauth/authorize/?response_type=code&client_id=${e(\n this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.clientId\n )}&scope=profile&state=${e(state)}&redirect_uri=${e(\n this.context.serverInfo.oAuthProviderInfos.InvisionCommunity.redirectUri\n )}`;\n break;\n }\n }\n\n const oauthdata = JSON.parse(\n window.sessionStorage.getItem(\"oauth\") ?? \"{}\"\n ) as OAuthStateStorage;\n oauthdata[state] = {\n provider: provider,\n url: this.props.location.pathname\n };\n\n window.sessionStorage.setItem(\"oauth\", JSON.stringify(oauthdata));\n\n window.location.href = url;\n\n return new Promise(resolve => resolve());\n }\n\n private async submit(event: FormEvent) {\n event.preventDefault();\n this.setState({\n busy: true\n });\n const response = await ServerClient.login({\n type: CredentialsType.Password,\n userName: this.state.username,\n password: this.state.password\n });\n if (response.code == StatusCode.ERROR) {\n this.setState({\n busy: false\n });\n this.addError(response.error);\n }\n }\n}\nLogin.contextType = GeneralContext;\nexport default withRouter(Login);\n","import React from \"react\";\n\nimport type { ServerInformationResponse, UserResponse } from \"../ApiClient/generatedcode/generated\";\nimport InternalError from \"../ApiClient/models/InternalComms/InternalError\";\n\nexport type GeneralContext = {\n deleteError: (error: InternalError) => void;\n errors: Set;\n user: UserResponse;\n serverInfo: ServerInformationResponse;\n};\n\n//same as GeneralContext except used for components which arent loading under the router so we cant guarentee that serverInfo and user wont be null\nexport type UnsafeGeneralContext = {\n deleteError: (error: InternalError) => void;\n errors: Set;\n user: UserResponse | null;\n serverInfo: ServerInformationResponse | null;\n};\n\nexport const GeneralContext = React.createContext(\n (undefined as unknown) as GeneralContext\n);\n","declare const API_VERSION: string;\ndeclare const VERSION: string;\ndeclare const MODE: \"DEV\" | \"PROD\" | \"GITHUB\";\ndeclare const DEFAULT_BASEPATH: string;\ndeclare const DEFAULT_APIPATH: string;\n\nconst _API_VERSION = API_VERSION;\nconst _VERSION = VERSION;\nconst _MODE = MODE;\nconst _DEFAULT_BASEPATH = DEFAULT_BASEPATH;\nconst _DEFAULT_APIPATH = DEFAULT_APIPATH;\n\nexport { _API_VERSION as API_VERSION };\nexport { _VERSION as VERSION };\nexport { _MODE as MODE };\nexport { _DEFAULT_BASEPATH as DEFAULT_BASEPATH };\nexport { _DEFAULT_APIPATH as DEFAULT_APIPATH };\n","declare let __webpack_public_path__: string;\ndeclare let MODE: \"GITHUB\" | \"PROD\" | \"DEV\";\n\n// eslint-disable-next-line prefer-const\nif (window.publicPath && MODE !== \"GITHUB\") __webpack_public_path__ = window.publicPath;\nconsole.log(\"Public path:\", __webpack_public_path__);\n\nexport {};\n","import api from \"!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../node_modules/css-loader/dist/cjs.js!./App.css\";\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import api from \"!../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../node_modules/css-loader/dist/cjs.js!./AppNavbar.css\";\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import \"./AppNavbar.css\";\n\nimport { faExclamationCircle } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React from \"react\";\nimport { NavDropdown, OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport Button from \"react-bootstrap/Button\";\nimport Dropdown from \"react-bootstrap/Dropdown\";\nimport Nav from \"react-bootstrap/Nav\";\nimport Navbar from \"react-bootstrap/Navbar\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\nimport { SemVer } from \"semver\";\n\nimport AdminClient from \"../ApiClient/AdminClient\";\nimport { AdministrationRights } from \"../ApiClient/generatedcode/generated\";\nimport { StatusCode } from \"../ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"../ApiClient/ServerClient\";\nimport UserClient from \"../ApiClient/UserClient\";\nimport LoginHooks from \"../ApiClient/util/LoginHooks\";\nimport { GeneralContext, UnsafeGeneralContext } from \"../contexts/GeneralContext\";\nimport { hasAdminRight, matchesPath, resolvePermissionSet } from \"../utils/misc\";\nimport RouteController from \"../utils/RouteController\";\nimport { AppCategories, AppRoute, AppRoutes } from \"../utils/routes\";\n\ninterface IProps extends RouteComponentProps {\n category?: {\n name: string;\n key: string;\n };\n loggedIn: boolean;\n}\n\ninterface IState {\n //so we dont actually use the routes but it allows us to make react update the component\n routes: AppRoute[];\n categories: typeof AppCategories;\n updateAvailable: boolean;\n}\n\nclass AppNavbar extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: IProps) {\n super(props);\n this.logoutClick = this.logoutClick.bind(this);\n this.loginSuccess = this.loginSuccess.bind(this);\n this.logout = this.logout.bind(this);\n this.refresh = this.refresh.bind(this);\n\n this.state = {\n routes: [],\n categories: AppCategories,\n updateAvailable: false\n };\n }\n\n private loginSuccess(): void {\n void this.checkShowServerUpdateIcon();\n }\n\n private async checkShowServerUpdateIcon(): Promise {\n await ServerClient.wait4Init();\n const userResponse = await UserClient.getCurrentUser();\n if (userResponse.code === StatusCode.ERROR) return;\n\n const user = userResponse.payload;\n\n const permissionSet = resolvePermissionSet(user);\n if (hasAdminRight(permissionSet, AdministrationRights.ChangeVersion)) {\n const response = await AdminClient.getAdminInfo();\n if (response.code == StatusCode.OK) {\n const latestVersion = new SemVer(response.payload.latestVersion);\n const currentVersion = new SemVer(this.context.serverInfo!.version);\n\n const updateAvailable = latestVersion.compare(currentVersion) === 1;\n\n this.setState({\n updateAvailable\n });\n }\n }\n }\n\n private logout() {\n this.setState({\n updateAvailable: false\n });\n }\n\n private refresh(routes: Array) {\n this.setState({\n routes\n });\n }\n\n public async componentDidMount(): Promise {\n LoginHooks.on(\"loginSuccess\", this.loginSuccess);\n ServerClient.on(\"logout\", this.logout);\n\n this.setState({\n routes: await RouteController.getRoutes()\n });\n\n RouteController.on(\"refresh\", this.refresh);\n }\n\n public componentWillUnmount(): void {\n LoginHooks.removeListener(\"loginSuccess\", this.loginSuccess);\n ServerClient.removeListener(\"logout\", this.logout);\n RouteController.removeListener(\"refresh\", this.refresh);\n }\n\n public render(): React.ReactNode {\n return (\n \n \n {\n this.props.history.push(AppRoutes.home.link ?? AppRoutes.home.route, {\n reload: true\n });\n }}\n className=\"mr-auto\">\n {this.renderVersion()}\n \n \n \n \n {this.state.updateAvailable ? (\n (\n \n \n \n )}>\n

\n \n this.props.history.push(\n AppRoutes.admin_update.link ??\n AppRoutes.admin_update.route,\n { reload: true }\n )\n }\n icon={faExclamationCircle}\n />\n

\n \n ) : (\n \n )}\n {this.renderUser()}\n
\n \n
\n );\n }\n\n private renderVersion(): React.ReactNode {\n if (!this.context.serverInfo?.version) {\n return ;\n }\n\n return (\n \n \n {\" v\"}\n {this.context.serverInfo.version}\n \n );\n }\n\n private renderUser(): React.ReactNode {\n if (!this.props.loggedIn)\n return (\n \n {\n this.props.history.push(\n AppRoutes.config.link ?? AppRoutes.config.route,\n { reload: true }\n );\n }}\n variant=\"primary\">\n \n \n {\n this.props.history.push(AppRoutes.info.link ?? AppRoutes.info.route, {\n reload: true\n });\n }}\n variant=\"primary\">\n \n \n \n );\n\n return (\n \n \n \n {this.context.user ? (\n this.context.user.name\n ) : (\n \n )}\n \n \n {\n this.props.history.push(\n AppRoutes.info.link ?? AppRoutes.info.route,\n { reload: true }\n );\n }}>\n \n \n {\n this.props.history.push(\n AppRoutes.config.link ?? AppRoutes.config.route,\n { reload: true }\n );\n }}>\n \n \n {AppRoutes.passwd.cachedAuth ? (\n {\n this.props.history.push(\n AppRoutes.passwd.link ?? AppRoutes.passwd.route,\n { reload: true }\n );\n }}>\n \n \n ) : (\n \"\"\n )}\n {\n ServerClient.emit(\"purgeCache\");\n this.props.history.replace(this.props.location.pathname, {\n reload: true\n });\n }}>\n \n \n {\n this.props.history.replace(this.props.location.pathname, {\n reload: true\n });\n }}>\n \n \n \n \n \n \n \n \n );\n }\n\n private logoutClick(): void {\n ServerClient.logout();\n }\n}\nAppNavbar.contextType = GeneralContext;\nexport default withRouter(AppNavbar);\n","import React from \"react\";\nimport { OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport { FormattedMessage } from \"react-intl\";\n\nimport logo from \"../images/logo.svg\";\n\ninterface IProps {}\n\ninterface IState {}\n\nexport default class Logo extends React.Component {\n public render(): React.ReactNode {\n let memeSelector = 4;\n return (\n {\n if (showing) {\n memeSelector = Math.round(Math.random() * 100) % 26;\n }\n }}\n overlay={props => (\n \n \n \n )}>\n \n \n );\n }\n}\n","import { faExclamationTriangle } from \"@fortawesome/free-solid-svg-icons\";\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport React from \"react\";\nimport { Button, OverlayTrigger, Tooltip } from \"react-bootstrap\";\nimport { FormattedMessage } from \"react-intl\";\n\ninterface IProps {}\n\ninterface IState {}\n\nexport default class ReportIssue extends React.Component {\n public render(): React.ReactNode {\n return (\n (\n \n \n \n )}>\n \n window.open(\n \"https://github.com/tgstation/tgstation-server-webpanel/issues/new\"\n )\n }>\n \n \n \n );\n }\n}\n","import React from \"react\";\nimport Card from \"react-bootstrap/Card\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\nimport { MODE, VERSION } from \"../../definitions/constants\";\n\ninterface IProps extends RouteComponentProps {}\ninterface IState {\n error?: Error;\n errorInfo?: React.ErrorInfo;\n}\n\nclass ErrorBoundary extends React.Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {};\n }\n\n public componentDidUpdate(prevProps: IProps): void {\n if (this.props.location.key !== prevProps.location.key) {\n this.setState({\n error: undefined,\n errorInfo: undefined\n });\n }\n }\n\n public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.setState({\n error,\n errorInfo\n });\n }\n\n public render(): React.ReactNode {\n if (this.state.error) {\n return (\n \n \n \n \n \n \n \n {this.state.error.name}: {this.state.error.message}\n \n \n \n {`Webpanel Version: ${VERSION}\\nWebpanel Mode: ${MODE}\\nStack trace: ${\n this.state.errorInfo?.componentStack ??\n \"Unable to get stack info\"\n }`}\n \n \n \n \n \n );\n } else {\n return this.props.children;\n }\n }\n}\n\nexport default withRouter(ErrorBoundary);\n","import { Component, ReactNode } from \"react\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\n\ninterface IProps\n extends RouteComponentProps<\n Record,\n {\n statusCode?: number;\n },\n { reload?: boolean }\n > {}\ninterface IState {\n clear: boolean;\n}\n\nclass Reload extends Component {\n public constructor(props: IProps) {\n super(props);\n this.state = {\n clear: false\n };\n }\n public componentDidUpdate(prevProps: IProps): void {\n if (this.state.clear) {\n this.setState({\n clear: false\n });\n return;\n }\n if (\n prevProps.match.path == this.props.match.path &&\n prevProps.location.key != this.props.location.key &&\n this.props.location.state?.reload\n ) {\n this.setState({\n clear: true\n });\n }\n }\n\n public render(): ReactNode {\n return this.state.clear ? \"\" : this.props.children;\n }\n}\n\nexport default withRouter(Reload);\n","import loadable, { LoadableComponent } from \"@loadable/component\";\nimport * as React from \"react\";\nimport { Component, ComponentClass, ReactNode } from \"react\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage } from \"react-intl\";\nimport { RouteComponentProps, withRouter } from \"react-router\";\nimport { Route, Switch } from \"react-router-dom\";\n\nimport { CredentialsType } from \"./ApiClient/models/ICredentials\";\nimport InternalError, { ErrorCode } from \"./ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"./ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"./ApiClient/ServerClient\";\nimport AccessDenied from \"./components/utils/AccessDenied\";\nimport ErrorAlert from \"./components/utils/ErrorAlert\";\nimport ErrorBoundary from \"./components/utils/ErrorBoundary\";\nimport Loading from \"./components/utils/Loading\";\nimport Reload from \"./components/utils/Reload\";\nimport Login, { OAuthStateStorage } from \"./components/views/Login\";\nimport { GeneralContext, UnsafeGeneralContext } from \"./contexts/GeneralContext\";\nimport { MODE } from \"./definitions/constants\";\nimport { matchesPath } from \"./utils/misc\";\nimport RouteController from \"./utils/RouteController\";\nimport { AppRoute, AppRoutes, RouteData } from \"./utils/routes\";\n\ninterface IState {\n loading: boolean;\n routes: Array;\n components: Map>;\n}\ninterface IProps extends RouteComponentProps {\n loggedIn: boolean;\n loggedOut: boolean;\n selectCategory: (category: string) => void;\n}\n\nconst LoadSpin = (page: string) => (\n \n \n \n);\n\nconst NotFound = loadable(() => import(\"./components/utils/NotFound\"), {\n fallback: LoadSpin(\"loading.page.notfound\")\n});\n\nclass Router extends Component {\n public declare context: UnsafeGeneralContext;\n public constructor(props: IProps) {\n super(props);\n\n this.refreshListener = this.refreshListener.bind(this);\n\n const components = new Map>();\n\n const routes = RouteController.getImmediateRoutes(false);\n routes.forEach(route => {\n components.set(\n route.name,\n //*should* always be a react component\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n loadable(() => import(`./components/views/${route.file}`), {\n fallback: LoadSpin(route.name)\n })\n );\n });\n\n this.state = {\n loading: !!new URLSearchParams(window.location.search).get(\"state\"),\n routes: RouteController.getImmediateRoutes(false),\n components: components\n };\n }\n\n private refreshListener(routes: Array) {\n this.setState({\n routes\n });\n }\n\n public async componentDidMount() {\n RouteController.on(\"refreshAll\", this.refreshListener);\n\n this.props.history.listen(location => {\n void this.listener(location.pathname);\n });\n this.listener(this.props.location.pathname);\n\n const URLSearch = new URLSearchParams(window.location.search);\n const state = URLSearch.get(\"state\");\n if (!state) {\n this.setState({\n loading: false\n });\n return;\n }\n\n if (MODE === \"PROD\" || MODE === \"GITHUB\") {\n window.history.replaceState(null, document.title, window.location.pathname);\n }\n\n const oauthdata = JSON.parse(\n window.sessionStorage.getItem(\"oauth\") ?? \"{}\"\n ) as OAuthStateStorage;\n\n const oauthstate = oauthdata[state];\n if (!oauthstate) {\n return this.setErrorAndEnd(\n new InternalError(ErrorCode.LOGIN_BAD_OAUTH, {\n jsError: Error(`State(${state}) cannot be resolved to a provider.`)\n })\n );\n }\n\n const code = URLSearch.get(\"code\");\n if (!code) {\n return this.setErrorAndEnd(\n new InternalError(ErrorCode.LOGIN_BAD_OAUTH, {\n jsError: Error(`Code not found.`)\n })\n );\n }\n this.props.history.replace(oauthstate.url);\n\n const response = await ServerClient.login({\n type: CredentialsType.OAuth,\n provider: oauthstate.provider,\n token: code\n });\n\n window.sessionStorage.removeItem(\"oauth\");\n\n if (response.code === StatusCode.OK) {\n this.setState({\n loading: false\n });\n } else {\n return this.setErrorAndEnd(response.error);\n }\n }\n\n public componentWillUnmount(): void {\n RouteController.removeListener(\"refreshAll\", this.refreshListener);\n }\n\n private setErrorAndEnd(error: InternalError) {\n RouteData.oautherrors = [error];\n this.setState({\n loading: false\n });\n }\n\n private listener(location: string) {\n const routes = RouteController.getImmediateRoutes(false);\n for (const route of routes) {\n if (route.category && route.navbarLoose && matchesPath(location, route.route)) {\n this.props.selectCategory(route.category);\n break;\n }\n }\n }\n\n public render(): ReactNode {\n if (this.state.loading) {\n return ;\n }\n\n return (\n \n \n
\n \n {this.state.routes.map(route => {\n if (!route.loginless && !this.props.loggedIn) return;\n\n return (\n {\n let Comp;\n\n if (!route.cachedAuth) {\n Comp = AccessDenied;\n } else {\n Comp = this.state.components.get(\n route.name\n )! as ComponentClass;\n }\n\n return !this.context?.user && !route.loginless ? (\n \n \n \n ) : //Yeah I have no excuse for this, I didn't want to implement a route config option\n // to allow a single route to work without server info so i added it as a check here\n !this.context?.serverInfo &&\n route != AppRoutes.config ? (\n \n \n \n ) : route.noContainer ? (\n \n \n \n ) : (\n \n \n \n );\n }}\n />\n );\n })}\n \n \n {this.props.loggedIn ? (\n \n ) : (\n \n )}\n \n \n \n
\n
\n
\n );\n }\n}\nRouter.contextType = GeneralContext;\nexport default withRouter(Router);\n","class Locales {\n public static readonly en: string = \"en\";\n}\n\nexport default Locales;\n","import ILocalization from \"./ILocalization\";\nimport ITranslation from \"./ITranslation\";\n\nexport default class Translation implements ITranslation {\n public constructor(public readonly locale: string, public readonly messages: ILocalization) {}\n}\n","import ITranslation from \"./ITranslation\";\nimport ITranslationFactory from \"./ITranslationFactory\";\nimport Locales from \"./Locales\";\nimport Translation from \"./Translation\";\n\nclass TranslationFactory implements ITranslationFactory {\n private static readonly fallbackLocale: string = Locales.en;\n\n private static getShortHandedLocale(locale: string): string {\n return locale.split(\"-\")[0];\n }\n\n public async loadTranslation(locale: string): Promise {\n //fancy type annotations but its just load the json file in this variable as a map of strings to strings\n const localization: { [key: string]: string } = (await import(\n `./locales/${locale}.json`\n )) as { [key: string]: string };\n\n if (!localization) {\n let shortHandedLocale = TranslationFactory.getShortHandedLocale(locale);\n if (shortHandedLocale === locale) {\n if (shortHandedLocale === TranslationFactory.fallbackLocale)\n throw new Error(\"Invalid locale: \" + locale);\n shortHandedLocale = TranslationFactory.fallbackLocale;\n }\n return await this.loadTranslation(shortHandedLocale);\n }\n\n let model: ITranslation | null = null;\n try {\n model = new Translation(locale, localization);\n } catch (e) {\n throw Error(`Error loading localization for locale '${locale}': ${JSON.stringify(e)}`);\n }\n\n return model;\n }\n}\n\nexport default TranslationFactory;\n","import \"./App.css\";\n\nimport * as React from \"react\";\nimport Alert from \"react-bootstrap/Alert\";\nimport Container from \"react-bootstrap/Container\";\nimport { FormattedMessage, IntlProvider } from \"react-intl\";\nimport { BrowserRouter } from \"react-router-dom\";\n\nimport Pkg from \"./../package.json\";\nimport InternalError, {\n ErrorCode,\n GenericErrors\n} from \"./ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"./ApiClient/models/InternalComms/InternalStatus\";\nimport ServerClient from \"./ApiClient/ServerClient\";\nimport UserClient from \"./ApiClient/UserClient\";\nimport CredentialsProvider from \"./ApiClient/util/CredentialsProvider\";\nimport LoginHooks from \"./ApiClient/util/LoginHooks\";\nimport AppNavbar from \"./components/AppNavbar\";\nimport Logo from \"./components/Logo\";\nimport ReportIssue from \"./components/ReportIssue\";\nimport ErrorAlert from \"./components/utils/ErrorAlert\";\nimport ErrorBoundary from \"./components/utils/ErrorBoundary\";\nimport JobsList from \"./components/utils/JobsList\";\nimport Loading from \"./components/utils/Loading\";\nimport { GeneralContext, UnsafeGeneralContext } from \"./contexts/GeneralContext\";\nimport { DEFAULT_BASEPATH } from \"./definitions/constants\";\nimport Router from \"./Router\";\nimport ITranslation from \"./translations/ITranslation\";\nimport ITranslationFactory from \"./translations/ITranslationFactory\";\nimport Locales from \"./translations/Locales\";\nimport TranslationFactory from \"./translations/TranslationFactory\";\n\ninterface IState {\n translation?: ITranslation;\n translationError?: string;\n loggedIn: boolean;\n loggedOut: boolean;\n loading: boolean;\n GeneralContextInfo: UnsafeGeneralContext;\n}\n\ninterface IProps {\n readonly locale: string;\n readonly translationFactory?: ITranslationFactory;\n}\n\ninterface InnerProps {\n loading: boolean;\n loggedIn: boolean;\n loggedOut: boolean;\n}\n\ninterface InnerState {\n passdownCat?: { name: string; key: string };\n}\n\nclass InnerApp extends React.Component {\n public declare context: UnsafeGeneralContext;\n\n public constructor(props: InnerProps) {\n super(props);\n\n this.state = {};\n }\n\n public componentDidMount() {\n document.title = \"TGS Webpanel v\" + Pkg.version;\n // I can't be assed to remember the default admin password\n document.addEventListener(\"keydown\", event => {\n if (event.key === \"L\" && event.ctrlKey && event.shiftKey) {\n ServerClient.logout();\n void ServerClient.login(CredentialsProvider.default);\n }\n });\n }\n\n public render(): React.ReactNode {\n return (\n \n \n \n {this.props.loading ? (\n \n \n \n ) : (\n <>\n \n \n \n \n \n
\n \n
\n {Array.from(this.context.errors.values()).map((value, idx) => {\n return (\n this.context.deleteError(value)}\n />\n );\n })}\n
\n {\n this.setState({\n passdownCat: {\n name: cat,\n key: Math.random().toString()\n }\n });\n }}\n />\n \n )}\n {this.props.loggedIn ? : null}\n
\n \n \n \n );\n }\n}\nInnerApp.contextType = GeneralContext;\n\nclass App extends React.Component {\n private readonly translationFactory: ITranslationFactory;\n\n public constructor(props: IProps) {\n super(props);\n\n this.finishLogin = this.finishLogin.bind(this);\n this.finishLogout = this.finishLogout.bind(this);\n this.updateContextUser = this.updateContextUser.bind(this);\n this.updateContextServer = this.updateContextServer.bind(this);\n this.deleteGeneralContextError = this.deleteGeneralContextError.bind(this);\n\n this.translationFactory = this.props.translationFactory ?? new TranslationFactory();\n\n this.state = {\n loggedIn: false,\n loggedOut: false,\n loading: true,\n GeneralContextInfo: {\n errors: new Set(),\n user: null,\n serverInfo: null,\n deleteError: this.deleteGeneralContextError\n }\n };\n }\n\n private async updateContextUser() {\n const response = await UserClient.getCurrentUser();\n if (response.code === StatusCode.OK) {\n this.setState(prev => {\n return {\n GeneralContextInfo: {\n errors: prev.GeneralContextInfo.errors,\n user: response.payload,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n deleteError: prev.GeneralContextInfo.deleteError\n }\n };\n });\n } else {\n if (response.error.code === ErrorCode.HTTP_ACCESS_DENIED) {\n this.setState(prev => {\n return {\n GeneralContextInfo: {\n user: null,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n deleteError: prev.GeneralContextInfo.deleteError,\n errors: prev.GeneralContextInfo.errors\n }\n };\n });\n } else {\n setTimeout(() => void this.updateContextUser(), 5000);\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.add(response.error);\n return {\n GeneralContextInfo: {\n errors: newSet,\n deleteError: prev.GeneralContextInfo.deleteError,\n user: null,\n serverInfo: prev.GeneralContextInfo.serverInfo\n }\n };\n });\n }\n }\n }\n\n private async updateContextServer(lastError?: InternalError) {\n const response = await ServerClient.getServerInfo();\n if (response.code === StatusCode.OK) {\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n if (lastError) {\n newSet.delete(lastError);\n }\n return {\n GeneralContextInfo: {\n errors: newSet,\n user: prev.GeneralContextInfo.user,\n serverInfo: response.payload,\n deleteError: prev.GeneralContextInfo.deleteError\n }\n };\n });\n } else {\n setTimeout(() => void this.updateContextServer(response.error), 5000);\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.add(response.error);\n if (lastError) {\n newSet.delete(lastError);\n }\n return {\n GeneralContextInfo: {\n errors: newSet,\n deleteError: prev.GeneralContextInfo.deleteError,\n user: prev.GeneralContextInfo.user,\n serverInfo: null\n }\n };\n });\n }\n }\n\n public deleteGeneralContextError(error: InternalError): void {\n this.setState(prev => {\n const newSet = new Set(prev.GeneralContextInfo.errors);\n newSet.delete(error);\n return {\n GeneralContextInfo: {\n deleteError: prev.GeneralContextInfo.deleteError,\n user: prev.GeneralContextInfo.user,\n serverInfo: prev.GeneralContextInfo.serverInfo,\n errors: newSet\n }\n };\n });\n }\n\n private finishLogin() {\n console.log(\"Logging in\");\n\n void this.updateContextUser().then(() =>\n this.setState({\n loggedIn: true,\n loading: false\n })\n );\n }\n\n private finishLogout() {\n this.setState({\n loggedIn: false,\n loggedOut: true\n });\n\n void this.updateContextUser();\n }\n public async componentDidMount(): Promise {\n LoginHooks.on(\"loginSuccess\", this.finishLogin);\n ServerClient.on(\"logout\", this.finishLogout);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.on(\"purgeCache\", this.updateContextServer);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.on(\"purgeCache\", this.updateContextUser);\n\n await this.loadTranslation();\n const loggedInSuccessfully = await ServerClient.initApi();\n await this.updateContextServer();\n if (loggedInSuccessfully) {\n await this.updateContextUser();\n }\n\n this.setState({\n loading: false,\n loggedIn: loggedInSuccessfully\n });\n }\n\n public componentWillUnmount(): void {\n LoginHooks.removeListener(\"loginSuccess\", this.finishLogin);\n ServerClient.removeListener(\"logout\", this.finishLogout);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.removeListener(\"purgeCache\", this.updateContextServer);\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n ServerClient.removeListener(\"purgeCache\", this.updateContextUser);\n }\n\n public render(): React.ReactNode {\n if (this.state.translationError) {\n return

{this.state.translationError}

;\n }\n\n if (!this.state.translation) {\n return Loading translations...;\n }\n return (\n \n \n \n \n \n );\n }\n\n private async loadTranslation(): Promise {\n console.time(\"LoadTranslations\");\n try {\n const translation = await this.translationFactory.loadTranslation(this.props.locale);\n this.setState({\n translation\n });\n } catch (error) {\n this.setState({\n translationError: JSON.stringify(error) ?? \"An unknown error occurred\"\n });\n\n return;\n }\n\n console.timeEnd(\"LoadTranslations\");\n }\n}\n\nexport default App;\n\nexport const IndexApp = (\n \n \n \n);\n","import { library } from \"@fortawesome/fontawesome-svg-core\";\nimport { faDiscord, faGithub } from \"@fortawesome/free-brands-svg-icons\";\nimport { faGitAlt } from \"@fortawesome/free-brands-svg-icons/faGitAlt\";\nimport {\n faArrowLeft,\n faCaretDown,\n faCaretRight,\n faClipboard,\n faComment,\n faExclamationTriangle,\n faFile,\n faFileAlt,\n faFolderMinus,\n faFolderPlus,\n faGamepad,\n faHashtag,\n faMinus,\n faUnlock,\n faUpload\n} from \"@fortawesome/free-solid-svg-icons\";\nimport { faAngleRight } from \"@fortawesome/free-solid-svg-icons/faAngleRight\";\nimport { faCheck } from \"@fortawesome/free-solid-svg-icons/faCheck\";\nimport { faCodeBranch } from \"@fortawesome/free-solid-svg-icons/faCodeBranch\";\nimport { faCogs } from \"@fortawesome/free-solid-svg-icons/faCogs\";\nimport { faComments } from \"@fortawesome/free-solid-svg-icons/faComments\";\nimport { faExclamationCircle } from \"@fortawesome/free-solid-svg-icons/faExclamationCircle\";\nimport { faFolderOpen } from \"@fortawesome/free-solid-svg-icons/faFolderOpen\";\nimport { faGripLinesVertical } from \"@fortawesome/free-solid-svg-icons/faGripLinesVertical\";\nimport { faHammer } from \"@fortawesome/free-solid-svg-icons/faHammer\";\nimport { faHdd } from \"@fortawesome/free-solid-svg-icons/faHdd\";\nimport { faHome } from \"@fortawesome/free-solid-svg-icons/faHome\";\nimport { faInfo } from \"@fortawesome/free-solid-svg-icons/faInfo\";\nimport { faInfoCircle } from \"@fortawesome/free-solid-svg-icons/faInfoCircle\";\nimport { faKey } from \"@fortawesome/free-solid-svg-icons/faKey\";\nimport { faListUl } from \"@fortawesome/free-solid-svg-icons/faListUl\";\nimport { faLock } from \"@fortawesome/free-solid-svg-icons/faLock\";\nimport { faPen } from \"@fortawesome/free-solid-svg-icons/faPen\";\nimport { faPlus } from \"@fortawesome/free-solid-svg-icons/faPlus\";\nimport { faQuestion } from \"@fortawesome/free-solid-svg-icons/faQuestion\";\nimport { faSearch } from \"@fortawesome/free-solid-svg-icons/faSearch\";\nimport { faServer } from \"@fortawesome/free-solid-svg-icons/faServer\";\nimport { faStream } from \"@fortawesome/free-solid-svg-icons/faStream\";\nimport { faSync } from \"@fortawesome/free-solid-svg-icons/faSync\";\nimport { faTimes } from \"@fortawesome/free-solid-svg-icons/faTimes\";\nimport { faTools } from \"@fortawesome/free-solid-svg-icons/faTools\";\nimport { faTrash } from \"@fortawesome/free-solid-svg-icons/faTrash\";\nimport { faUndo } from \"@fortawesome/free-solid-svg-icons/faUndo\";\nimport { faUser } from \"@fortawesome/free-solid-svg-icons/faUser\";\nimport { faUsers } from \"@fortawesome/free-solid-svg-icons/faUsers\";\nimport { faUserSlash } from \"@fortawesome/free-solid-svg-icons/faUserSlash\";\n\nexport default function (): void {\n library.add(\n faCheck,\n faTimes,\n faExclamationCircle,\n faUser,\n faUserSlash,\n faHdd,\n faSync,\n faPlus,\n faQuestion,\n faHome,\n faTools,\n faCogs,\n faUndo,\n faInfo,\n faGripLinesVertical,\n faAngleRight,\n faKey,\n faPen,\n faGithub,\n faDiscord,\n faTrash,\n faInfoCircle,\n faGitAlt,\n faHammer,\n faListUl,\n faComments,\n faFolderOpen,\n faUsers,\n faCodeBranch,\n faSearch,\n faServer,\n faStream,\n faLock,\n faMinus,\n faUnlock,\n faLock,\n faCaretRight,\n faCaretDown,\n faComment,\n faHashtag,\n faFolderPlus,\n faFolderMinus,\n faFile,\n faFileAlt,\n faExclamationTriangle,\n faClipboard,\n faArrowLeft,\n faAngleRight,\n faUpload,\n faGamepad\n );\n}\n","// eslint-disable-next-line\nimport \"./publicPath\";\n\n// definition files\n// css\nimport \"./styles/dark.scss\";\n// polyfills\nimport \"@formatjs/intl-relativetimeformat/polyfill\";\nimport \"@formatjs/intl-relativetimeformat/locale-data/en\";\nimport \"@formatjs/intl-pluralrules/polyfill\";\nimport \"@formatjs/intl-pluralrules/locale-data/en\";\n\nimport ReactDOM from \"react-dom\";\n\nimport ConfigController from \"./ApiClient/util/ConfigController\";\nimport JobsController from \"./ApiClient/util/JobsController\";\nimport { IndexApp } from \"./App\";\nimport { MODE, VERSION } from \"./definitions/constants\";\nimport initIcons from \"./utils/icolibrary\";\n\n// dont lag the dom\ninitIcons();\nConfigController.loadconfig();\nJobsController.init();\n\nif (window.loadedChannelFromWebpack && MODE !== \"DEV\") {\n alert(\n \"Warning: channel.json was served from bundled files instead of TGS, the webpanel is running from the local version instead of the github update repo.\\nPlease report this to your server host.\\nIf you are the server host, please report this to alexkar598#2712 on discord\\n\\nWebpanel version: \" +\n VERSION\n );\n}\n\n// At some point, the webpanel had the ability to save passwords, this is however,\n// insecure as compromised webhosts can lead to code being served from an untrusted source,\n// leaking the saved password. Makes sure it's not there anymore\ntry {\n window.localStorage.removeItem(\"username\");\n window.sessionStorage.removeItem(\"username\");\n window.localStorage.removeItem(\"password\");\n window.sessionStorage.removeItem(\"password\");\n} catch {\n (() => {})();\n}\n\nfunction mountApp() {\n ReactDOM.render(IndexApp, document.getElementById(\"root\"));\n}\n\nwindow.addEventListener(\"DOMContentLoaded\", mountApp);\nif (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n mountApp();\n}\n","import { TypedEmitter } from \"tiny-typed-emitter/lib\";\n\nimport LoginHooks from \"../ApiClient/util/LoginHooks\";\nimport {\n AppCategories,\n AppRoute,\n AppRoutes,\n UnpopulatedAppCategories,\n UnpopulatedAppCategory\n} from \"./routes\";\n\ninterface IEvents {\n refresh: (routes: Array) => void; //auth\n refreshAll: (routes: Array) => void; //noauth+auth\n}\n\n//helper class to process AppRoutes\nclass RouteController extends TypedEmitter {\n private refreshing = false;\n\n public constructor() {\n super();\n window.rtcontroller = this;\n this.refreshRoutes = this.refreshRoutes.bind(this);\n\n LoginHooks.addHook(this.refreshRoutes);\n this.refreshRoutes().catch(console.error);\n\n //process categories\n console.time(\"Category mapping\");\n const catmap = new Map();\n\n for (const [name, val] of Object.entries(UnpopulatedAppCategories)) {\n val.routes = [];\n //null asserted the name because that one is everywhere, even if the rest is partial\n catmap.set(val.name!, val);\n //@ts-expect-error typescript cannot infer that the name is a key of UnpopulatedAppCategories\n AppCategories[name] = val;\n }\n\n for (const route of Object.values(AppRoutes)) {\n if (!route.category) continue;\n\n const cat = catmap.get(route.category);\n if (!cat) {\n console.error(\"Route has invalid category\", route);\n continue;\n }\n\n //this is guaranteed to be an array as its set in the loop above\n cat.routes!.push(route);\n\n if (route.catleader) {\n if (cat.leader) {\n console.error(\"Category has two leaders\", cat.leader, route);\n continue;\n }\n cat.leader = route;\n }\n }\n console.log(\"Categories mapped\", catmap);\n console.timeEnd(\"Category mapping\");\n }\n\n public async refreshRoutes() {\n if (this.refreshing) {\n console.log(\"Already refreshing\");\n return;\n } //no need to refresh twice\n\n this.refreshing = true;\n\n const work = []; // we get all hidden routes no matter the authentification without waiting for the refresh\n const routes = this.getImmediateRoutes(false);\n\n for (const route of routes) {\n route.cachedAuth = undefined;\n if (route.isAuthorized) {\n work.push(\n route.isAuthorized().then(auth => {\n route.cachedAuth = auth;\n })\n );\n } else {\n route.cachedAuth = true;\n }\n }\n\n await Promise.all(work); //wait for all the authorized calls to complete\n\n this.emit(\"refresh\", this.getImmediateRoutes(true));\n const routesNoAuth = this.getImmediateRoutes(false);\n this.emit(\"refreshAll\", routesNoAuth);\n this.refreshing = false;\n\n console.log(\"Routes refreshed\", routesNoAuth);\n return await this.getRoutes();\n }\n\n private wait4refresh() {\n return new Promise(resolve => {\n if (!this.refreshing) {\n resolve();\n return;\n }\n this.on(\"refresh\", () => {\n resolve();\n });\n });\n }\n\n public async getRoutes(auth = true): Promise {\n await this.wait4refresh();\n\n return this.getImmediateRoutes(auth);\n }\n\n public getImmediateRoutes(auth = true) {\n const results: Array = [];\n\n for (const val of Object.values(AppRoutes)) {\n //we check for isauthorized here without calling because routes that lack the function are public\n if (val.isAuthorized && !val.cachedAuth && auth) continue; //if not authorized and we only show authorized routes\n\n results.push(val);\n }\n\n return results;\n }\n}\n\nexport default new RouteController();\n","import { pathToRegexp } from \"path-to-regexp\";\n\nimport {\n AdministrationRights,\n ChatBotRights,\n ConfigurationRights,\n DreamDaemonRights,\n DreamMakerRights,\n EngineRights,\n InstanceManagerRights,\n InstancePermissionSetResponse,\n InstancePermissionSetRights,\n PermissionSet,\n RepositoryRights,\n UserResponse\n} from \"../ApiClient/generatedcode/generated\";\n\nexport type DistributiveOmit = T extends T ? Omit : never;\n\nfunction download(filename: string, text: string): void {\n const element = document.createElement(\"a\");\n element.setAttribute(\"href\", \"data:text/plain;charset=utf-8,\" + encodeURIComponent(text));\n element.setAttribute(\"download\", filename);\n\n element.style.display = \"none\";\n document.body.appendChild(element);\n\n element.click();\n\n document.body.removeChild(element);\n}\n\nfunction replaceAll(str: string, find: string, replace: string, ignore?: boolean): string {\n return str.replace(\n new RegExp(find.replace(/([/,!\\\\^${}[\\]().*+?|<>\\-&])/g, \"\\\\$&\"), ignore ? \"gi\" : \"g\"),\n replace.replace(/\\$/g, \"$$$$\")\n );\n}\n\nfunction matchesPath(path: string, target: string, exact = false): boolean {\n //remove trailing slashes\n if (path.slice(-1) === \"/\") path = path.slice(0, -1);\n if (target.slice(-1) === \"/\") target = target.slice(0, -1);\n\n return pathToRegexp(target, undefined, { end: exact }).test(path);\n}\n\nfunction resolvePermissionSet(user: UserResponse): PermissionSet {\n return (user.permissionSet ?? user.group?.permissionSet) as PermissionSet;\n}\n\nfunction bitflagIsTrue(bitfield: number, bitflag: number): boolean {\n return !!(bitflag & bitfield);\n}\n\nfunction hasAdminRight(permissionSet: PermissionSet, right: AdministrationRights): boolean {\n return bitflagIsTrue(permissionSet.administrationRights, right);\n}\n\nfunction hasInstanceManagerRight(\n permissionSet: PermissionSet,\n right: InstanceManagerRights\n): boolean {\n return bitflagIsTrue(permissionSet.instanceManagerRights, right);\n}\n\nfunction hasEngineRight(\n permissionSet: InstancePermissionSetResponse,\n right: EngineRights\n): boolean {\n return bitflagIsTrue(permissionSet.engineRights, right);\n}\n\nfunction hasChatBotRight(\n permissionSet: InstancePermissionSetResponse,\n right: ChatBotRights\n): boolean {\n return bitflagIsTrue(permissionSet.chatBotRights, right);\n}\n\nfunction hasConfigRight(\n permissionSet: InstancePermissionSetResponse,\n right: ConfigurationRights\n): boolean {\n return bitflagIsTrue(permissionSet.configurationRights, right);\n}\n\nfunction hasDreamDaemonRight(\n permissionSet: InstancePermissionSetResponse,\n right: DreamDaemonRights\n): boolean {\n return bitflagIsTrue(permissionSet.dreamDaemonRights, right);\n}\n\nfunction hasDreamMakerRight(\n permissionSet: InstancePermissionSetResponse,\n right: DreamMakerRights\n): boolean {\n return bitflagIsTrue(permissionSet.dreamMakerRights, right);\n}\n\nfunction hasInstancePermRight(\n permissionSet: InstancePermissionSetResponse,\n right: InstancePermissionSetRights\n): boolean {\n return bitflagIsTrue(permissionSet.instancePermissionSetRights, right);\n}\n\nfunction hasRepoRight(\n permissionSet: InstancePermissionSetResponse,\n right: RepositoryRights\n): boolean {\n return bitflagIsTrue(permissionSet.repositoryRights, right);\n}\n\nfunction hasFilesRight(\n permissionSet: InstancePermissionSetResponse,\n right: ConfigurationRights\n): boolean {\n return bitflagIsTrue(permissionSet.configurationRights, right);\n}\n\nexport {\n download,\n replaceAll,\n matchesPath,\n resolvePermissionSet,\n bitflagIsTrue,\n hasAdminRight,\n hasEngineRight,\n hasConfigRight,\n hasRepoRight,\n hasChatBotRight,\n hasInstancePermRight,\n hasInstanceManagerRight,\n hasDreamMakerRight,\n hasDreamDaemonRight,\n hasFilesRight\n};\n","import { IconProp } from \"@fortawesome/fontawesome-svg-core\";\n\nimport { AdministrationRights, InstanceManagerRights } from \"../ApiClient/generatedcode/generated\";\nimport InternalError, { ErrorCode } from \"../ApiClient/models/InternalComms/InternalError\";\nimport { StatusCode } from \"../ApiClient/models/InternalComms/InternalStatus\";\nimport UserClient from \"../ApiClient/UserClient\";\nimport CredentialsProvider from \"../ApiClient/util/CredentialsProvider\";\nimport { resolvePermissionSet } from \"./misc\";\n\nexport interface AppRoute {\n ///Base parameters\n //must be unique, also is the id of the route name message\n name: string;\n //must be unique, url to access\n route: string;\n //link to link to when linking to the route, defaults to the \"route\"\n link?: string;\n //filename in components/view that the route should display\n file: string;\n\n ///Path parameters\n //If subpaths should route here\n loose: boolean;\n //If subpaths should light up the navbar button\n navbarLoose: boolean;\n\n ///Authentication\n //if we can route to it even on the login page\n loginless?: boolean;\n //function to tell if we are authorized\n isAuthorized?: () => Promise;\n //result of isAuthorized() after RouteController runs it, can be used by components but only set by RouteController\n cachedAuth?: boolean;\n\n ///Visibility\n //if this shows up on the navbar\n visibleNavbar: boolean;\n //serves two purposes, first one is to give it an icon, the second one is to not display it if the icon is undefined\n homeIcon?: IconProp;\n\n ///Categories\n //name of the category it belongs to\n category?: string;\n //if this is the main button in the category\n catleader?: boolean;\n\n ///Misc\n //Should we not wrap this component in a ?\n noContainer?: boolean;\n}\n\nfunction adminRight(right: AdministrationRights) {\n return async (): Promise => {\n if (!CredentialsProvider.hasToken()) return false;\n const response = await UserClient.getCurrentUser();\n\n if (response.code == StatusCode.OK) {\n return !!(resolvePermissionSet(response.payload).administrationRights & right);\n }\n return false;\n };\n}\n\nfunction instanceManagerRight(right: InstanceManagerRights) {\n return async (): Promise => {\n if (!CredentialsProvider.hasToken()) return false;\n const response = await UserClient.getCurrentUser();\n\n if (response.code == StatusCode.OK) {\n return !!(resolvePermissionSet(response.payload).instanceManagerRights & right);\n }\n return false;\n };\n}\n\n//https://stackoverflow.com/questions/54598322/how-to-make-typescript-infer-the-keys-of-an-object-but-define-type-of-its-value\n//Infer the keys but restrict the values to a type\nconst asElementTypesAppRoute = (et: { [K in keyof T]: AppRoute }) => et;\n\nconst AppRoutes = asElementTypesAppRoute({\n home: {\n name: \"routes.home\",\n route: \"/\",\n file: \"Home\",\n\n loose: false,\n navbarLoose: false,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"home\",\n catleader: true\n },\n instancecreate: {\n name: \"routes.instancecreate\",\n route: \"/instances/create\",\n file: \"Instance/Create\",\n\n loose: false,\n navbarLoose: false,\n\n isAuthorized: instanceManagerRight(InstanceManagerRights.Create),\n\n visibleNavbar: false,\n\n category: \"instance\",\n catleader: false\n },\n instancelist: {\n name: \"routes.instancelist\",\n route: \"/instances/\",\n file: \"Instance/List\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: instanceManagerRight(InstanceManagerRights.List | InstanceManagerRights.Read),\n\n visibleNavbar: true,\n homeIcon: \"hdd\",\n\n category: \"instance\",\n catleader: true\n },\n instanceedit: {\n name: \"routes.instanceedit\",\n route: \"/instances/edit/:id(\\\\d+)/:tab?/\",\n file: \"Instance/InstanceEdit\",\n\n get link(): string {\n return RouteData.selectedinstanceid !== undefined\n ? `/instances/edit/${RouteData.selectedinstanceid}/${\n RouteData.selectedinstanceedittab !== undefined\n ? `${RouteData.selectedinstanceedittab}/`\n : \"\"\n }`\n : AppRoutes.instancelist.link ?? AppRoutes.instancelist.route;\n },\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"instance\"\n },\n instancejobs: {\n name: \"routes.instancejobs\",\n route: \"/instances/jobs/\",\n file: \"Instance/Jobs\",\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"instance\"\n },\n userlist: {\n name: \"routes.usermanager\",\n route: \"/users/\",\n file: \"User/List\",\n\n loose: false,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: \"user\",\n\n category: \"user\",\n catleader: true\n },\n useredit: {\n name: \"routes.useredit\",\n route: \"/users/edit/user/:id(\\\\d+)/:tab?/\",\n\n //whole lot of bullshit just to make it that if you have an id, link to the edit page, otherwise link to the list page, and if you link to the user page, put the tab in\n get link(): string {\n return RouteData.selecteduserid !== undefined\n ? `/users/edit/user/${RouteData.selecteduserid}/${\n RouteData.selectedusertab !== undefined ? `${RouteData.selectedusertab}/` : \"\"\n }`\n : AppRoutes.userlist.link ?? AppRoutes.userlist.route;\n },\n file: \"User/Edit\",\n\n loose: true,\n navbarLoose: true,\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"user\"\n },\n usercreate: {\n name: \"routes.usercreate\",\n route: \"/users/create/\",\n\n link: \"/users/create/\",\n file: \"User/Create\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.WriteUsers),\n\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"user\"\n },\n admin: {\n name: \"routes.admin\",\n route: \"/admin/\",\n file: \"Administration\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: adminRight(\n AdministrationRights.ChangeVersion |\n AdministrationRights.DownloadLogs |\n AdministrationRights.UploadVersion\n ),\n\n visibleNavbar: true,\n homeIcon: \"tools\",\n\n category: \"admin\",\n catleader: true\n },\n admin_update: {\n name: \"routes.admin.update\",\n route: \"/admin/update/:all?/\",\n file: \"Admin/Update\",\n\n link: \"/admin/update/\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(\n AdministrationRights.ChangeVersion | AdministrationRights.UploadVersion\n ),\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"admin\"\n },\n admin_logs: {\n name: \"routes.admin.logs\",\n route: \"/admin/logs/:name?/\",\n link: \"/admin/logs/\",\n file: \"Admin/Logs\",\n\n loose: false,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.DownloadLogs),\n visibleNavbar: true,\n homeIcon: undefined,\n\n category: \"admin\",\n\n noContainer: true\n },\n passwd: {\n name: \"routes.passwd\",\n route: \"/users/passwd/:id(\\\\d+)?/\",\n link: \"/users/passwd/\",\n file: \"ChangePassword\",\n\n loose: true,\n navbarLoose: true,\n\n isAuthorized: adminRight(AdministrationRights.EditOwnPassword),\n\n visibleNavbar: false,\n homeIcon: \"key\"\n },\n config: {\n name: \"routes.config\",\n route: \"/config/\",\n file: \"Configuration\",\n\n loose: true,\n navbarLoose: true,\n\n loginless: true,\n\n visibleNavbar: false,\n homeIcon: \"cogs\"\n },\n setup: {\n name: \"routes.setup\",\n route: \"/setup/\",\n file: \"Setup\",\n\n loose: true,\n navbarLoose: true,\n\n loginless: true,\n\n visibleNavbar: false\n },\n oAuth: {\n name: \"routes.oauth\",\n route: \"/oauth/:provider?/\",\n file: \"Login\",\n\n loose: true,\n navbarLoose: false,\n\n loginless: true,\n\n visibleNavbar: false\n },\n info: {\n name: \"routes.info\",\n route: \"/info\",\n file: \"Info\",\n\n loose: false,\n navbarLoose: false,\n\n loginless: true,\n\n visibleNavbar: true,\n homeIcon: \"info-circle\",\n\n category: undefined,\n catleader: false\n }\n});\n\nexport { AppRoutes };\n\n//https://stackoverflow.com/questions/54598322/how-to-make-typescript-infer-the-keys-of-an-object-but-define-type-of-its-value\n//Infer the keys but restrict the values to a type\nconst asElementTypesCategory = (et: { [K in keyof T]: UnpopulatedAppCategory }) => et;\n\nexport type UnpopulatedAppCategory = Partial;\n\nexport interface AppCategory {\n name: string; //doesnt really matter, kinda bullshit\n routes: AppRoute[];\n leader: AppRoute;\n}\n\nexport const UnpopulatedAppCategories = asElementTypesCategory({\n home: {\n name: \"home\"\n },\n instance: {\n name: \"instance\"\n },\n user: {\n name: \"user\"\n },\n admin: {\n name: \"admin\"\n }\n});\n\n// @ts-expect-error This is populated in the constructor after its populated\nexport const AppCategories: { [K in keyof typeof UnpopulatedAppCategories]: AppCategory } = {};\n\nexport const RouteData = {\n selecteduserid: undefined as undefined | number,\n selectedusertab: undefined as undefined | string,\n\n selectedinstanceid: undefined as undefined | number,\n selectedinstanceedittab: undefined as undefined | string,\n\n instancelistpage: undefined as undefined | number,\n loglistpage: undefined as undefined | number,\n byondlistpage: undefined as undefined | number,\n userlistpage: undefined as undefined | number,\n jobhistorypage: new Map(),\n\n oautherrors: [] as InternalError[]\n};\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../node_modules/css-loader/dist/runtime/cssWithMappingToString.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/css-loader/dist/runtime/api.js\";\nimport ___CSS_LOADER_GET_URL_IMPORT___ from \"../node_modules/css-loader/dist/runtime/getUrl.js\";\nimport ___CSS_LOADER_URL_IMPORT_0___ from \"./logo.svg\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".App {\\n background-size: 50%;\\n background: #1e1e1e url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \") no-repeat center;\\n position: absolute;\\n width: 100%;\\n top: 0;\\n bottom: 0;\\n display: grid;\\n}\\n\\n.App-error {\\n color: red;\\n font-size: 150%;\\n margin: auto;\\n}\\n\\n.App-main {\\n display: grid;\\n}\\n\\n.Root {\\n overflow: hidden;\\n display: grid;\\n grid-template-rows: 9% auto;\\n}\\n\\n.Root-login {\\n display: grid;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://./src/App.css\"],\"names\":[],\"mappings\":\"AAAA;IACI,oBAAoB;IACpB,4EAAkD;IAClD,kBAAkB;IAClB,WAAW;IACX,MAAM;IACN,SAAS;IACT,aAAa;AACjB;;AAEA;IACI,UAAU;IACV,eAAe;IACf,YAAY;AAChB;;AAEA;IACI,aAAa;AACjB;;AAEA;IACI,gBAAgB;IAChB,aAAa;IACb,2BAA2B;AAC/B;;AAEA;IACI,aAAa;AACjB\",\"sourcesContent\":[\".App {\\n background-size: 50%;\\n background: #1e1e1e url(logo.svg) no-repeat center;\\n position: absolute;\\n width: 100%;\\n top: 0;\\n bottom: 0;\\n display: grid;\\n}\\n\\n.App-error {\\n color: red;\\n font-size: 150%;\\n margin: auto;\\n}\\n\\n.App-main {\\n display: grid;\\n}\\n\\n.Root {\\n overflow: hidden;\\n display: grid;\\n grid-template-rows: 9% auto;\\n}\\n\\n.Root-login {\\n display: grid;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../../node_modules/css-loader/dist/runtime/cssWithMappingToString.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".tgs-update-notification {\\n color: #66ff07;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://./src/components/AppNavbar.css\"],\"names\":[],\"mappings\":\"AAAA;IACI,cAAc;AAClB\",\"sourcesContent\":[\".tgs-update-notification {\\n color: #66ff07;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","var map = {\n\t\"./Admin/Logs\": [\n\t\t43408,\n\t\t171,\n\t\t408\n\t],\n\t\"./Admin/Logs.tsx\": [\n\t\t43408,\n\t\t171,\n\t\t408\n\t],\n\t\"./Admin/Update\": [\n\t\t80732,\n\t\t171,\n\t\t578,\n\t\t6,\n\t\t724,\n\t\t732\n\t],\n\t\"./Admin/Update.tsx\": [\n\t\t80732,\n\t\t171,\n\t\t578,\n\t\t6,\n\t\t724,\n\t\t732\n\t],\n\t\"./Administration\": [\n\t\t29363,\n\t\t171,\n\t\t363\n\t],\n\t\"./Administration.tsx\": [\n\t\t29363,\n\t\t171,\n\t\t363\n\t],\n\t\"./ChangePassword\": [\n\t\t61304,\n\t\t799\n\t],\n\t\"./ChangePassword.tsx\": [\n\t\t61304,\n\t\t799\n\t],\n\t\"./Configuration\": [\n\t\t67671,\n\t\t671\n\t],\n\t\"./Configuration.tsx\": [\n\t\t67671,\n\t\t671\n\t],\n\t\"./Home\": [\n\t\t59638,\n\t\t638\n\t],\n\t\"./Home.tsx\": [\n\t\t59638,\n\t\t638\n\t],\n\t\"./Info\": [\n\t\t41051,\n\t\t171,\n\t\t51\n\t],\n\t\"./Info.tsx\": [\n\t\t41051,\n\t\t171,\n\t\t51\n\t],\n\t\"./Instance/Create\": [\n\t\t38747,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t578,\n\t\t6,\n\t\t747\n\t],\n\t\"./Instance/Create.tsx\": [\n\t\t38747,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t578,\n\t\t6,\n\t\t747\n\t],\n\t\"./Instance/Edit/ChatBots\": [\n\t\t90740,\n\t\t767,\n\t\t171,\n\t\t740,\n\t\t318\n\t],\n\t\"./Instance/Edit/ChatBots.tsx\": [\n\t\t90740,\n\t\t767,\n\t\t171,\n\t\t740,\n\t\t318\n\t],\n\t\"./Instance/Edit/Config\": [\n\t\t62685,\n\t\t171,\n\t\t685\n\t],\n\t\"./Instance/Edit/Config.tsx\": [\n\t\t62685,\n\t\t171,\n\t\t685\n\t],\n\t\"./Instance/Edit/Deployment\": [\n\t\t44298,\n\t\t899,\n\t\t856,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t356\n\t],\n\t\"./Instance/Edit/Deployment.tsx\": [\n\t\t44298,\n\t\t899,\n\t\t856,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t356\n\t],\n\t\"./Instance/Edit/Engine\": [\n\t\t32240,\n\t\t899,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t657\n\t],\n\t\"./Instance/Edit/Engine.tsx\": [\n\t\t32240,\n\t\t899,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t657\n\t],\n\t\"./Instance/Edit/Files\": [\n\t\t20926,\n\t\t637,\n\t\t171,\n\t\t926,\n\t\t608\n\t],\n\t\"./Instance/Edit/Files.tsx\": [\n\t\t20926,\n\t\t637,\n\t\t171,\n\t\t926,\n\t\t608\n\t],\n\t\"./Instance/Edit/InstancePermissions\": [\n\t\t87345,\n\t\t803,\n\t\t171,\n\t\t345,\n\t\t246\n\t],\n\t\"./Instance/Edit/InstancePermissions.tsx\": [\n\t\t87345,\n\t\t803,\n\t\t171,\n\t\t345,\n\t\t246\n\t],\n\t\"./Instance/Edit/JobHistory\": [\n\t\t25921,\n\t\t171,\n\t\t921\n\t],\n\t\"./Instance/Edit/JobHistory.tsx\": [\n\t\t25921,\n\t\t171,\n\t\t921\n\t],\n\t\"./Instance/Edit/Repository\": [\n\t\t18264,\n\t\t856,\n\t\t611,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t264,\n\t\t233\n\t],\n\t\"./Instance/Edit/Repository.tsx\": [\n\t\t18264,\n\t\t856,\n\t\t611,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t264,\n\t\t233\n\t],\n\t\"./Instance/Edit/Server\": [\n\t\t86046,\n\t\t899,\n\t\t756,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t792\n\t],\n\t\"./Instance/Edit/Server.tsx\": [\n\t\t86046,\n\t\t899,\n\t\t756,\n\t\t171,\n\t\t578,\n\t\t240,\n\t\t67,\n\t\t792\n\t],\n\t\"./Instance/InstanceEdit\": [\n\t\t9182,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t803,\n\t\t767,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t165,\n\t\t240,\n\t\t67,\n\t\t264,\n\t\t740,\n\t\t926,\n\t\t345,\n\t\t182\n\t],\n\t\"./Instance/InstanceEdit.tsx\": [\n\t\t9182,\n\t\t899,\n\t\t856,\n\t\t637,\n\t\t756,\n\t\t611,\n\t\t803,\n\t\t767,\n\t\t171,\n\t\t578,\n\t\t757,\n\t\t165,\n\t\t240,\n\t\t67,\n\t\t264,\n\t\t740,\n\t\t926,\n\t\t345,\n\t\t182\n\t],\n\t\"./Instance/Jobs\": [\n\t\t41818,\n\t\t818\n\t],\n\t\"./Instance/Jobs.tsx\": [\n\t\t41818,\n\t\t818\n\t],\n\t\"./Instance/List\": [\n\t\t70670,\n\t\t171,\n\t\t670\n\t],\n\t\"./Instance/List.tsx\": [\n\t\t70670,\n\t\t171,\n\t\t670\n\t],\n\t\"./Login\": [\n\t\t9310\n\t],\n\t\"./Login.tsx\": [\n\t\t9310\n\t],\n\t\"./Setup\": [\n\t\t12757,\n\t\t666\n\t],\n\t\"./Setup.tsx\": [\n\t\t12757,\n\t\t666\n\t],\n\t\"./User/Create\": [\n\t\t14898,\n\t\t898\n\t],\n\t\"./User/Create.tsx\": [\n\t\t14898,\n\t\t898\n\t],\n\t\"./User/Edit\": [\n\t\t11404,\n\t\t803,\n\t\t171,\n\t\t404\n\t],\n\t\"./User/Edit.tsx\": [\n\t\t11404,\n\t\t803,\n\t\t171,\n\t\t404\n\t],\n\t\"./User/List\": [\n\t\t8746,\n\t\t171,\n\t\t746\n\t],\n\t\"./User/List.tsx\": [\n\t\t8746,\n\t\t171,\n\t\t746\n\t]\n};\nfunction webpackAsyncContext(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\treturn Promise.resolve().then(function() {\n\t\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\t\te.code = 'MODULE_NOT_FOUND';\n\t\t\tthrow e;\n\t\t});\n\t}\n\n\tvar ids = map[req], id = ids[0];\n\treturn Promise.all(ids.slice(1).map(__webpack_require__.e)).then(function() {\n\t\treturn __webpack_require__(id);\n\t});\n}\nwebpackAsyncContext.keys = function() { return Object.keys(map); };\nwebpackAsyncContext.id = 66235;\nmodule.exports = webpackAsyncContext;","var map = {\n\t\"./en.json\": [\n\t\t2422,\n\t\t422\n\t]\n};\nfunction webpackAsyncContext(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\treturn Promise.resolve().then(function() {\n\t\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\t\te.code = 'MODULE_NOT_FOUND';\n\t\t\tthrow e;\n\t\t});\n\t}\n\n\tvar ids = map[req], id = ids[0];\n\treturn __webpack_require__.e(ids[1]).then(function() {\n\t\treturn __webpack_require__.t(id, 3 | 16);\n\t});\n}\nwebpackAsyncContext.keys = function() { return Object.keys(map); };\nwebpackAsyncContext.id = 862;\nmodule.exports = webpackAsyncContext;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = function(chunkId) {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".\" + {\"6\":\"8be2a144a2793570315b\",\"51\":\"018c826056e10797488e\",\"67\":\"d56fce4f5250e06063bf\",\"165\":\"ba83e5a7e5ce7342f32c\",\"171\":\"91936e72280a3d6afb03\",\"182\":\"863e8e49ed9d42931724\",\"233\":\"bca5af88bf85dd9388cf\",\"240\":\"9d4d9c96f62ce48cce5f\",\"246\":\"67f9678139a3c824ae5c\",\"264\":\"6bc80619e126b48208c1\",\"318\":\"262f7ca0e51a961f2197\",\"345\":\"463f4581d9005d5a1f8f\",\"356\":\"8162c10f9d74465626fb\",\"363\":\"d935e8c7446b49ba6955\",\"370\":\"d23700bdb9e4969823c6\",\"404\":\"a8f6c9632768de417c6c\",\"408\":\"67a24726289357584bda\",\"422\":\"de987a83a6a343eed949\",\"578\":\"b30e90acaefb17f3a915\",\"608\":\"cdba29b0c970b4473f8d\",\"611\":\"9c4e031ea3c5676ddc02\",\"637\":\"67f97d250d5cc7c88b41\",\"638\":\"22ae8c317d679c0fbe03\",\"657\":\"ebfcf366f770219cda87\",\"666\":\"466319a2ec0bfe75619b\",\"670\":\"f23545dfb1fa34187665\",\"671\":\"d70d3c2a0ea3e94cdaa3\",\"685\":\"d842923a8b4fb2925914\",\"724\":\"37608abcbb4a745b53ee\",\"732\":\"26d1b1972e8409d84e2b\",\"740\":\"ff042fc269d749115529\",\"746\":\"24e79402e2a97d9d230f\",\"747\":\"a7a7de444f0f31df471a\",\"756\":\"3ea7efa9992f327c4f5e\",\"757\":\"cd59822a9cafc5472de2\",\"767\":\"2226708e286478875491\",\"792\":\"d0502c03902c2b81a3bc\",\"799\":\"5f592f7ea70157f79ee3\",\"803\":\"e84dc0cafa5ef825835a\",\"818\":\"de10c00448eedd21537d\",\"856\":\"4c7ce7828aeda2beaebd\",\"898\":\"506fa6125431a4f5701d\",\"899\":\"40a4d85a43cf77edbe66\",\"921\":\"d5768a750d900f116d8c\",\"926\":\"9721f261bd7f5a7449df\"}[chunkId] + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"https://tgstation.github.io/tgstation-server-webpanel/webpanel/5.7.0/\";","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t179: 0\n};\n\n__webpack_require__.f.j = function(chunkId, promises) {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = function(event) {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t} else installedChunks[chunkId] = 0;\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunktgstation_server_control_panel\"] = self[\"webpackChunktgstation_server_control_panel\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [607,338,340], function() { return __webpack_require__(16143); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","leafPrototypes","getProto","inProgress","dataWebpackPrefix","AccessDenied","React","render","title","variant","className","onClick","this","props","history","goBack","id","withRouter","ErrorAlert","Component","constructor","super","state","popup","error","handleClose","setState","dismissible","onClose","code","centered","show","onHide","size","closeButton","desc","type","DescType","VERSION","MODE","API_VERSION","extendedInfo","replace","addError","setErrors","prevState","errors","Array","from","push","displayErrors","map","err","index","key","prev","newarr","undefined","GenericAlert","body","children","JobError","open","setOpen","useState","Button","values","info","job","errorCode","TGSErrorCode","Modal","description","exceptionDetails","JobCard","createddate","Date","startedAt","createddiff","getTime","now","stoppeddiff","stoppedAt","cancelled","Toast","style","maxWidth","width","ToastHeader","ToastBody","stage","OverlayTrigger","overlay","Tooltip","toLocaleString","ref","triggerHandler","value","numeric","updateIntervalInSeconds","startedBy","name","cancelledBy","height","ProgressBar","animated","label","progress","toString","striped","canCancel","padding","onCancel","icon","JobsList","widgetRef","currentTimeout","handleUpdate","bind","jobs","JobsController","nextRetrySeconds","ownerrors","loading","instances","Map","current","scrollTop","componentDidMount","componentWillUnmount","clearTimeout","getSeconds","setTimeout","widget","nested","display","totalJobs","configOptions","jobsWidgetOptions","length","position","top","bottom","right","left","pointerEvents","zIndex","default","x","document","documentElement","clientWidth","Math","min","y","clientHeight","minHeight","minWidth","bounds","text","seconds","sort","a","b","instanceid","jobMap","xFinishedEnabled","forEach","instanceHeaderStyle","marginTop","marginLeft","renderTooltip","get","amount","placement","faTimes","defaultProps","Loading","animation","center","widthUnit","otherprops","styles","appear","classNames","addEndListener","node","done","addEventListener","Login","submit","console","log","RouteData","busy","validated","username","password","window","sessionStorage","getItem","CredentialsProvider","CredentialsType","tryLoginDefault","loggedOut","ServerClient","StatusCode","redirectSetup","context","serverInfo","providers","OAuthProvider","faGithub","faDiscord","src","alt","faInvision","providersTheme","GitHub","Discord","TGForums","Keycloak","InvisionCommunity","Col","lg","md","Card","Form","onSubmit","controlId","placeholder","onChange","event","target","required","block","oAuthProviderInfos","Object","keys","provider","ptheme","background","startOAuth","InternalError","ErrorCode","jsError","Error","stateArray","Uint8Array","crypto","getRandomValues","dec","padStart","join","url","e","encodeURIComponent","clientId","redirectUri","serverUrl","oauthdata","JSON","parse","location","pathname","setItem","stringify","href","Promise","resolve","preventDefault","response","userName","contextType","GeneralContext","_API_VERSION","_VERSION","_MODE","_DEFAULT_BASEPATH","DEFAULT_BASEPATH","_DEFAULT_APIPATH","DEFAULT_APIPATH","publicPath","__webpack_public_path__","options","AppNavbar","logoutClick","loginSuccess","logout","refresh","routes","categories","AppCategories","updateAvailable","checkShowServerUpdateIcon","userResponse","UserClient","user","payload","permissionSet","resolvePermissionSet","hasAdminRight","AdministrationRights","AdminClient","latestVersion","SemVer","currentVersion","version","compare","LoginHooks","RouteController","Navbar","expand","loggedIn","collapseOnSelect","bg","AppRoutes","reload","renderVersion","Nav","cat","leader","cachedAuth","link","route","active","matchesPath","navbarLoose","NavDropdown","filter","val","catleader","visibleNavbar","faExclamationCircle","renderUser","Dropdown","alignRight","Logo","memeSelector","onToggle","showing","round","random","ReportIssue","faExclamationTriangle","ErrorBoundary","componentDidUpdate","prevProps","errorInfo","componentDidCatch","Container","border","message","as","componentStack","Reload","clear","match","path","LoadSpin","page","NotFound","loadable","fallback","Router","refreshListener","components","set","file","URLSearchParams","search","listen","listener","URLSearch","replaceState","oauthstate","setErrorAndEnd","token","removeItem","category","selectCategory","loginless","exact","loose","Comp","noContainer","Locales","en","Translation","locale","messages","TranslationFactory","split","localization","shortHandedLocale","getShortHandedLocale","fallbackLocale","loadTranslation","model","InnerApp","Pkg","ctrlKey","shiftKey","basename","URL","passdownCat","Alert","idx","deleteError","App","translationFactory","finishLogin","finishLogout","updateContextUser","updateContextServer","deleteGeneralContextError","GeneralContextInfo","Set","newSet","add","lastError","delete","then","loggedInSuccessfully","translationError","translation","defaultLocale","time","timeEnd","IndexApp","library","faCheck","faUser","faUserSlash","faHdd","faSync","faPlus","faQuestion","faHome","faTools","faCogs","faUndo","faInfo","faGripLinesVertical","faAngleRight","faKey","faPen","faTrash","faInfoCircle","faGitAlt","faHammer","faListUl","faComments","faFolderOpen","faUsers","faCodeBranch","faSearch","faServer","faStream","faLock","faMinus","faUnlock","faCaretRight","faCaretDown","faComment","faHashtag","faFolderPlus","faFolderMinus","faFile","faFileAlt","faClipboard","faArrowLeft","faUpload","faGamepad","ConfigController","loadedChannelFromWebpack","alert","localStorage","mountApp","ReactDOM","getElementById","readyState","TypedEmitter","refreshing","rtcontroller","refreshRoutes","catch","catmap","entries","UnpopulatedAppCategories","work","getImmediateRoutes","isAuthorized","auth","all","emit","routesNoAuth","getRoutes","wait4refresh","on","results","download","filename","element","createElement","setAttribute","appendChild","click","removeChild","replaceAll","str","find","ignore","RegExp","slice","pathToRegexp","end","test","group","bitflagIsTrue","bitfield","bitflag","administrationRights","hasInstanceManagerRight","instanceManagerRights","hasEngineRight","engineRights","hasChatBotRight","chatBotRights","hasDreamDaemonRight","dreamDaemonRights","hasDreamMakerRight","dreamMakerRights","hasInstancePermRight","instancePermissionSetRights","hasRepoRight","repositoryRights","hasFilesRight","configurationRights","adminRight","async","instanceManagerRight","home","homeIcon","instancecreate","InstanceManagerRights","instancelist","instanceedit","selectedinstanceid","selectedinstanceedittab","instancejobs","userlist","useredit","selecteduserid","selectedusertab","usercreate","admin","admin_update","admin_logs","passwd","config","setup","oAuth","instance","instancelistpage","loglistpage","byondlistpage","userlistpage","jobhistorypage","oautherrors","___CSS_LOADER_EXPORT___","___CSS_LOADER_URL_REPLACEMENT_0___","module","webpackAsyncContext","req","__webpack_require__","o","ids","exports","t","__webpack_module_cache__","moduleId","cachedModule","__webpack_modules__","call","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","fulfilled","j","every","splice","r","n","getter","__esModule","d","getPrototypeOf","obj","__proto__","mode","ns","create","def","indexOf","getOwnPropertyNames","definition","defineProperty","enumerable","f","chunkId","reduce","promises","u","g","globalThis","Function","prop","prototype","hasOwnProperty","l","script","needAttach","scripts","getElementsByTagName","s","getAttribute","charset","timeout","nc","onScriptComplete","onerror","onload","doneFns","parentNode","head","Symbol","toStringTag","p","installedChunks","installedChunkData","promise","reject","errorType","realSrc","request","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/webpanel/5.7.0/webpanelmanifest.json b/webpanel/5.7.0/webpanelmanifest.json index 4ff5cd8b..ca5e1646 100644 --- a/webpanel/5.7.0/webpanelmanifest.json +++ b/webpanel/5.7.0/webpanelmanifest.json @@ -1 +1 @@ -{"manifestVersion":1,"version":"5.7.0","entries":["607.592e15ea131f62401699.bundle.js","338.3abb1cf4078b59bacaa5.bundle.js","340.60c7f09c8654c822471d.bundle.js","main.23756f40ad9814a1e9eb.bundle.js"]} \ No newline at end of file +{"manifestVersion":1,"version":"5.7.0","entries":["607.592e15ea131f62401699.bundle.js","338.3abb1cf4078b59bacaa5.bundle.js","340.60c7f09c8654c822471d.bundle.js","main.1049fd3651ff85aa05ae.bundle.js"]} \ No newline at end of file