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 {
text-decoration: underline;
}
.error-display .success {
color: green;
}

View File

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

View File

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

View File

@ -19,7 +19,8 @@ import {
import { ApiEndpoint, HttpCode } from "../../common/http"
import { normalize } from "../../common/util"
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 {
process?: cp.ChildProcess
@ -42,6 +43,7 @@ export class ApiHttpProvider extends HttpProvider {
public constructor(
options: HttpProviderOptions,
private readonly server: HttpServer,
private readonly vscode: VscodeHttpProvider,
private readonly dataDir?: string,
) {
super(options)
@ -256,17 +258,24 @@ export class ApiHttpProvider extends HttpProvider {
/**
* 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))
const session = this.sessions.get(sessionId)
if (!session) {
throw new Error("session does not exist")
switch (sessionId) {
case "vscode":
await this.vscode.dispose()
return { code: HttpCode.Ok }
default: {
const session = this.sessions.get(sessionId)
if (!session) {
throw new Error("session does not exist")
}
if (session.process) {
session.process.kill()
}
this.sessions.delete(sessionId)
return { code: HttpCode.Ok }
}
}
if (session.process) {
session.process.kill()
}
this.sessions.delete(sessionId)
return { code: HttpCode.Ok }
}
/**
@ -350,10 +359,20 @@ export class ApiHttpProvider extends HttpProvider {
*/
public async running(): Promise<RunningResponse> {
return {
applications: Array.from(this.sessions).map(([sessionId, session]) => ({
...session.app,
sessionId,
})),
applications: (this.vscode.running
? [
{
...Vscode,
sessionId: "vscode",
},
]
: []
).concat(
Array.from(this.sessions).map(([sessionId, session]) => ({
...session.app,
sessionId,
})),
),
}
}

View File

@ -124,7 +124,9 @@ export class MainHttpProvider extends HttpProvider {
}
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 {
@ -141,7 +143,7 @@ export class MainHttpProvider extends HttpProvider {
app.sessionId
? `<form class="kill-form" action="./delete" method="POST">
<input type="hidden" name="sessionId" value="${app.sessionId}">
<button class="kill" type="submit">Kill</button>
<button class="kill -button" type="submit">Kill</button>
</form>`
: ""
}

View File

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

View File

@ -31,6 +31,19 @@ export class VscodeHttpProvider extends HttpProvider {
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> {
const id = generateUuid()
const vscode = await this.fork()
@ -126,7 +139,8 @@ export class VscodeHttpProvider extends HttpProvider {
} catch (error) {
const message = `<div>VS Code failed to load.</div> ${
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}`
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 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"])
httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args)
httpServer.registerHttpProvider("/login", LoginHttpProvider)
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
}
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
* appropriate provider for that endpoint then write out the response. It also
@ -417,6 +421,13 @@ export class HttpServer {
a1: A1,
a2: A2,
): 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
public registerHttpProvider(endpoint: string, provider: any, ...args: any[]): any {
endpoint = endpoint.replace(/^\/+|\/+$/g, "")