mirror of https://github.com/coder/code-server.git
Add the ability to kill running VS Code instance
This commit is contained in:
parent
fd65cadaea
commit
21cfeb9da0
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})),
|
})),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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, "")
|
||||||
|
|
Loading…
Reference in New Issue