Add the ability to kill running VS Code instance

This commit is contained in:
Asher 2020-02-27 12:04:23 -06:00
parent fd65cadaea
commit 21cfeb9da0
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
9 changed files with 96 additions and 58 deletions

View File

@ -26,3 +26,7 @@
.error-display > .links > .link:hover { .error-display > .links > .link:hover {
text-decoration: underline; text-decoration: underline;
} }
.error-display .success {
color: green;
}

View File

@ -32,28 +32,15 @@
} }
.block-row > .item > .icon.-missing { .block-row > .item > .icon.-missing {
background-color: rgb(87, 114, 245); background-color: rgba(87, 114, 245, 0.2);
color: #fff;
text-align: center; text-align: center;
} }
.block-row > .item > .icon.-missing::after {
content: "?";
font-size: 0.7rem;
vertical-align: middle;
}
.kill-form { .kill-form {
display: inline-block; display: inline-block;
} }
.kill-form > .kill { .kill-form > .kill {
background-color: rgb(87, 114, 245); border-radius: 3px;
border: none; padding: 2px 5px;
color: #fff;
cursor: pointer;
font-size: 1rem;
line-height: 1rem;
margin: 0;
padding: 0;
} }

View File

@ -20,27 +20,36 @@
</head> </head>
<body> <body>
<div class="center-container"> <div class="center-container">
<!-- TEMP: Only VS Code for now. --> <div class="card-box">
<!-- <div class="info-block"> --> <div class="header">
<!-- <h2 class="header">Running Applications</h2> --> <h2 class="main">Running</h2>
<!-- {{APP_LIST:RUNNING}} --> <div class="sub">Currently running applications.</div>
<!-- </div> --> </div>
<div class="content">
{{APP_LIST:RUNNING}}
</div>
</div>
<div class="card-box"> <div class="card-box">
<div class="header"> <div class="header">
<h2 class="main">Launch</h2> <h2 class="main">Editors</h2>
<div class="sub">Choose an application to launch below.</div> <div class="sub">Choose an editor to launch below.</div>
</div> </div>
<div class="content"> <div class="content">
<div class="block-row"> {{APP_LIST:EDITORS}}
<a class="item -link" href="./vscode">
<img class="icon" src="./static-{{COMMIT}}/lib/vscode/resources/linux/code.png" />
VS Code
</a>
</div>
</div> </div>
</div> </div>
<!-- <div class="card-box"> -->
<!-- <div class="header"> -->
<!-- <h2 class="main">Other</h2> -->
<!-- <div class="sub">Choose an application to launch below.</div> -->
<!-- </div> -->
<!-- <div class="content"> -->
<!-- {{APP_LIST:OTHER}} -->
<!-- </div> -->
<!-- </div> -->
<div class="card-box"> <div class="card-box">
<div class="header"> <div class="header">
<h2 class="main">Version</h2> <h2 class="main">Version</h2>
@ -50,16 +59,6 @@
{{UPDATE:NAME}} {{UPDATE:NAME}}
</div> </div>
</div> </div>
<!-- <div class="info-block"> -->
<!-- <h2 class="header">Editors</h2> -->
<!-- {{APP_LIST:EDITORS}} -->
<!-- </div> -->
<!-- <div class="info-block"> -->
<!-- <h2 class="header">Other</h2> -->
<!-- {{APP_LIST:OTHER}} -->
<!-- </div> -->
</div> </div>
</body> </body>
</html> </html>

View File

@ -19,7 +19,8 @@ import {
import { ApiEndpoint, HttpCode } from "../../common/http" import { ApiEndpoint, HttpCode } from "../../common/http"
import { normalize } from "../../common/util" import { normalize } from "../../common/util"
import { HttpProvider, HttpProviderOptions, HttpResponse, HttpServer, Route } from "../http" import { HttpProvider, HttpProviderOptions, HttpResponse, HttpServer, Route } from "../http"
import { findApplications, findWhitelistedApplications } from "./bin" import { findApplications, findWhitelistedApplications, Vscode } from "./bin"
import { VscodeHttpProvider } from "./vscode"
interface ServerSession { interface ServerSession {
process?: cp.ChildProcess process?: cp.ChildProcess
@ -42,6 +43,7 @@ export class ApiHttpProvider extends HttpProvider {
public constructor( public constructor(
options: HttpProviderOptions, options: HttpProviderOptions,
private readonly server: HttpServer, private readonly server: HttpServer,
private readonly vscode: VscodeHttpProvider,
private readonly dataDir?: string, private readonly dataDir?: string,
) { ) {
super(options) super(options)
@ -256,8 +258,13 @@ export class ApiHttpProvider extends HttpProvider {
/** /**
* Kill a session identified by `app.sessionId`. * Kill a session identified by `app.sessionId`.
*/ */
public deleteSession(sessionId: string): HttpResponse { public async deleteSession(sessionId: string): Promise<HttpResponse> {
logger.debug("deleting session", field("sessionId", sessionId)) logger.debug("deleting session", field("sessionId", sessionId))
switch (sessionId) {
case "vscode":
await this.vscode.dispose()
return { code: HttpCode.Ok }
default: {
const session = this.sessions.get(sessionId) const session = this.sessions.get(sessionId)
if (!session) { if (!session) {
throw new Error("session does not exist") throw new Error("session does not exist")
@ -268,6 +275,8 @@ export class ApiHttpProvider extends HttpProvider {
this.sessions.delete(sessionId) this.sessions.delete(sessionId)
return { code: HttpCode.Ok } return { code: HttpCode.Ok }
} }
}
}
/** /**
* Create a new session and return the session ID. * Create a new session and return the session ID.
@ -350,10 +359,20 @@ export class ApiHttpProvider extends HttpProvider {
*/ */
public async running(): Promise<RunningResponse> { public async running(): Promise<RunningResponse> {
return { return {
applications: Array.from(this.sessions).map(([sessionId, session]) => ({ applications: (this.vscode.running
? [
{
...Vscode,
sessionId: "vscode",
},
]
: []
).concat(
Array.from(this.sessions).map(([sessionId, session]) => ({
...session.app, ...session.app,
sessionId, sessionId,
})), })),
),
} }
} }

View File

@ -124,7 +124,9 @@ export class MainHttpProvider extends HttpProvider {
} }
private getAppRows(apps: ReadonlyArray<Application>): string { private getAppRows(apps: ReadonlyArray<Application>): string {
return apps.length > 0 ? apps.map((app) => this.getAppRow(app)).join("\n") : `<div class="none">None</div>` return apps.length > 0
? apps.map((app) => this.getAppRow(app)).join("\n")
: `<div class="none">No applications are currently running.</div>`
} }
private getAppRow(app: Application): string { private getAppRow(app: Application): string {
@ -141,7 +143,7 @@ export class MainHttpProvider extends HttpProvider {
app.sessionId app.sessionId
? `<form class="kill-form" action="./delete" method="POST"> ? `<form class="kill-form" action="./delete" method="POST">
<input type="hidden" name="sessionId" value="${app.sessionId}"> <input type="hidden" name="sessionId" value="${app.sessionId}">
<button class="kill" type="submit">Kill</button> <button class="kill -button" type="submit">Kill</button>
</form>` </form>`
: "" : ""
} }

View File

@ -1,3 +1,4 @@
import * as fs from "fs"
import * as path from "path" import * as path from "path"
import { Application } from "../../common/api" import { Application } from "../../common/api"
@ -11,6 +12,7 @@ const getVscodeVersion = (): string => {
export const Vscode: Application = { export const Vscode: Application = {
categories: ["Editor"], categories: ["Editor"],
icon: fs.readFileSync(path.resolve(__dirname, "../../../lib/vscode/resources/linux/code.png")).toString("base64"),
installed: true, installed: true,
name: "VS Code", name: "VS Code",
path: "/vscode", path: "/vscode",

View File

@ -31,6 +31,19 @@ export class VscodeHttpProvider extends HttpProvider {
this.serverRootPath = path.join(this.vsRootPath, "out/vs/server") this.serverRootPath = path.join(this.vsRootPath, "out/vs/server")
} }
public get running(): boolean {
return !!this._vscode
}
public async dispose(): Promise<void> {
if (this._vscode) {
const vscode = await this._vscode
vscode.removeAllListeners()
this._vscode = undefined
vscode.kill()
}
}
private async initialize(options: VscodeOptions): Promise<WorkbenchOptions> { private async initialize(options: VscodeOptions): Promise<WorkbenchOptions> {
const id = generateUuid() const id = generateUuid()
const vscode = await this.fork() const vscode = await this.fork()
@ -126,7 +139,8 @@ export class VscodeHttpProvider extends HttpProvider {
} catch (error) { } catch (error) {
const message = `<div>VS Code failed to load.</div> ${ const message = `<div>VS Code failed to load.</div> ${
this.isDev this.isDev
? "<div>It might not have finished compiling.</div>Check for 'Finished compilation' in the output." ? `<div>It might not have finished compiling.</div>` +
`Check for <code>Finished <span class="success">compilation</span></code> in the output.`
: "" : ""
} <br><br>${error}` } <br><br>${error}`
return this.getErrorRoot(route, "VS Code failed to load", "500", message) return this.getErrorRoot(route, "VS Code failed to load", "500", message)

View File

@ -44,9 +44,9 @@ const main = async (args: Args): Promise<void> => {
} }
const httpServer = new HttpServer(options) const httpServer = new HttpServer(options)
const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer, args["user-data-dir"]) const vscode = httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args)
const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer, vscode, args["user-data-dir"])
const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"]) const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"])
httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args)
httpServer.registerHttpProvider("/login", LoginHttpProvider) httpServer.registerHttpProvider("/login", LoginHttpProvider)
httpServer.registerHttpProvider("/", MainHttpProvider, api, update) httpServer.registerHttpProvider("/", MainHttpProvider, api, update)

View File

@ -360,6 +360,10 @@ export interface HttpProvider2<A1, A2, T> {
new (options: HttpProviderOptions, a1: A1, a2: A2): T new (options: HttpProviderOptions, a1: A1, a2: A2): T
} }
export interface HttpProvider3<A1, A2, A3, T> {
new (options: HttpProviderOptions, a1: A1, a2: A2, a3: A3): T
}
/** /**
* An HTTP server. Its main role is to route incoming HTTP requests to the * An HTTP server. Its main role is to route incoming HTTP requests to the
* appropriate provider for that endpoint then write out the response. It also * appropriate provider for that endpoint then write out the response. It also
@ -417,6 +421,13 @@ export class HttpServer {
a1: A1, a1: A1,
a2: A2, a2: A2,
): T ): T
public registerHttpProvider<A1, A2, A3, T extends HttpProvider>(
endpoint: string,
provider: HttpProvider3<A1, A2, A3, T>,
a1: A1,
a2: A2,
a3: A3,
): T
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
public registerHttpProvider(endpoint: string, provider: any, ...args: any[]): any { public registerHttpProvider(endpoint: string, provider: any, ...args: any[]): any {
endpoint = endpoint.replace(/^\/+|\/+$/g, "") endpoint = endpoint.replace(/^\/+|\/+$/g, "")