mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Merge pull request #11 from lancatlin/server-socket-graph
New features: 1. collect 2 levels file list when user type 'ls' in container 2. send the file graph event via socket Breaking changes: 1. change the channel of terminal connection from 'message' to 'terminal' 3. move client.js to standalone package packages/client 4. change frontend api to call the same origin instead, setup vite proxy for developement. (Now it's localhost on default cuz the server updates includes in this PR as well)
This commit is contained in:
23 files changed
+266
-107
No files matched your search
@@ -48,8 +48,11 @@ docker compose -f docker-compose.build.yml push
|
|||||||
Source: `app/`
|
Source: `app/`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
# Connect to dev server (https://odyssey.wancat.cc)
|
||||||
yarn app
|
yarn app
|
||||||
# Open http://localhost:5173/
|
|
||||||
|
# Connect to localhost (http://localhost:3000)
|
||||||
|
yarn app:local
|
||||||
```
|
```
|
||||||
|
|
||||||
## Swagger
|
## Swagger
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "API_TARGET=https://odyssey.wancat.cc vite",
|
||||||
|
"dev:local": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ onMounted(async () => {
|
|||||||
socket.on('disconnect', function close() {
|
socket.on('disconnect', function close() {
|
||||||
console.log('Disconnected from the server.')
|
console.log('Disconnected from the server.')
|
||||||
})
|
})
|
||||||
|
socket.on('message', console.log)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ onMounted(() => {
|
|||||||
term.open(terminal.value)
|
term.open(terminal.value)
|
||||||
term.focus()
|
term.focus()
|
||||||
resizeScreen()
|
resizeScreen()
|
||||||
socket.on('message', (message) => {
|
socket.on('terminal', (message) => {
|
||||||
term.write(message)
|
term.write(message)
|
||||||
})
|
})
|
||||||
term.onData((key) => {
|
term.onData((key) => {
|
||||||
socket.send(key)
|
socket.emit('terminal', key)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: 'https://odyssey.wancat.cc/api/v1',
|
baseURL: '/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import io from 'socket.io-client'
|
import io from 'socket.io-client'
|
||||||
|
|
||||||
const host = 'wss://odyssey.wancat.cc'
|
|
||||||
|
|
||||||
class SocketWrapper {
|
class SocketWrapper {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.socket = null
|
this.socket = null
|
||||||
@@ -14,7 +12,7 @@ class SocketWrapper {
|
|||||||
this.socket.disconnect()
|
this.socket.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket = io(host, {
|
this.socket = io('', {
|
||||||
query: {
|
query: {
|
||||||
session_id: session._id,
|
session_id: session._id,
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-2
@@ -7,8 +7,13 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
// target: 'http://localhost:3000', // the server url
|
target: `${process.env.API_TARGET || 'http://localhost:3000'}/api/v1`,
|
||||||
target: 'http://odyssey.wancat.cc ',
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
},
|
||||||
|
'/socket.io': {
|
||||||
|
target: process.env.API_TARGET || 'ws://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -12,8 +12,9 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"app": "yarn workspace app dev",
|
"app": "yarn workspace app dev",
|
||||||
|
"app:local": "yarn workspace app dev:local",
|
||||||
"server": "yarn workspace server dev",
|
"server": "yarn workspace server dev",
|
||||||
"cli": "yarn workspace server cli",
|
"cli": "yarn workspace client start",
|
||||||
"lint": "eslint --ext .js,.vue .",
|
"lint": "eslint --ext .js,.vue .",
|
||||||
"format": "prettier --ignore-path .prettierignore --write app server"
|
"format": "prettier --ignore-path .prettierignore --write app server"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cli",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {}
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,14 @@
|
|||||||
import { stdin, stdout, exit } from 'process'
|
import { stdin, stdout, exit } from 'process'
|
||||||
import { io } from 'socket.io-client'
|
import { io } from 'socket.io-client'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import { get } from './utils/env.js'
|
|
||||||
|
|
||||||
|
function get(key, defaultValue) {
|
||||||
|
const value = process.env[key]
|
||||||
|
if (value != null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
stdin.setRawMode(true)
|
stdin.setRawMode(true)
|
||||||
stdin.resume()
|
stdin.resume()
|
||||||
stdin.setEncoding('utf8')
|
stdin.setEncoding('utf8')
|
||||||
@@ -60,10 +66,17 @@ async function connect(sessionId) {
|
|||||||
console.log('Connected to the server.')
|
console.log('Connected to the server.')
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('message', function incoming(data) {
|
socket.on('message', console.log)
|
||||||
|
|
||||||
|
socket.on('terminal', function incoming(data) {
|
||||||
stdout.write(data)
|
stdout.write(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('graph', (data) => {
|
||||||
|
console.log('receive graph:')
|
||||||
|
console.log(data)
|
||||||
|
})
|
||||||
|
|
||||||
socket.on('close', function close() {
|
socket.on('close', function close() {
|
||||||
console.log('Disconnected from the server.')
|
console.log('Disconnected from the server.')
|
||||||
exit()
|
exit()
|
||||||
@@ -74,7 +87,7 @@ async function connect(sessionId) {
|
|||||||
socket.close()
|
socket.close()
|
||||||
exit()
|
exit()
|
||||||
} else {
|
} else {
|
||||||
socket.send(key)
|
socket.emit('terminal', key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^11.0.0",
|
||||||
|
"socket.io-client": "^4.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
const fs = require('fs').promises
|
||||||
|
const { exit } = require('process')
|
||||||
|
const minimist = require('minimist')
|
||||||
|
const { colorize, printResponses } = require('./print.js')
|
||||||
|
const discoverFiles = require('./discover.js')
|
||||||
|
|
||||||
|
async function readOrNone(file) {
|
||||||
|
try {
|
||||||
|
return await fs.readFile(file, 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandListeners = {
|
||||||
|
ls: [discoverFiles],
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCommand(command) {
|
||||||
|
const argv = minimist(command.split(' '))
|
||||||
|
const name = argv._[0]
|
||||||
|
const listeners = commandListeners[name]
|
||||||
|
if (!listeners) return {}
|
||||||
|
return listeners.reduce(async (result, listener) => {
|
||||||
|
const res = await listener(argv)
|
||||||
|
return { ...res, ...result }
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const {
|
||||||
|
API_ENDPOINT,
|
||||||
|
TOKEN,
|
||||||
|
PWD,
|
||||||
|
CMD_NAME,
|
||||||
|
CMD_OUTPUT_FILE,
|
||||||
|
CMD_ERROR_FILE,
|
||||||
|
CMD_EXIT_CODE,
|
||||||
|
} = process.env
|
||||||
|
if (!CMD_NAME) exit(0)
|
||||||
|
|
||||||
|
const additionalData = await handleCommand(CMD_NAME)
|
||||||
|
|
||||||
|
const output = await readOrNone(CMD_OUTPUT_FILE)
|
||||||
|
const error = await readOrNone(CMD_ERROR_FILE)
|
||||||
|
const payload = {
|
||||||
|
command: CMD_NAME,
|
||||||
|
output,
|
||||||
|
error,
|
||||||
|
exit_code: CMD_EXIT_CODE,
|
||||||
|
token: TOKEN,
|
||||||
|
pwd: PWD,
|
||||||
|
...additionalData,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_ENDPOINT}/api/v1/commands`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.responses) {
|
||||||
|
await printResponses(data.responses, 60)
|
||||||
|
}
|
||||||
|
if (data.hints) {
|
||||||
|
for (const hint of data.hints) {
|
||||||
|
console.log(colorize(hint, 'yellow'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.end) {
|
||||||
|
console.log(colorize('Quest completed!', 'blue'))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(exit)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
const fs = require('fs').promises
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
async function collectFilesInfo(inputPath, level = 0, hiddenFiles = false) {
|
||||||
|
const stats = await fs.stat(inputPath)
|
||||||
|
const name = path.basename(inputPath)
|
||||||
|
const discovered = level <= 1 && (hiddenFiles || !name.startsWith('.'))
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
path: inputPath,
|
||||||
|
name,
|
||||||
|
type: stats.isDirectory() ? 'folder' : 'file',
|
||||||
|
discovered,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (stats.isDirectory() && level < 2) {
|
||||||
|
try {
|
||||||
|
let entries = await fs.readdir(inputPath)
|
||||||
|
if (level > 0) {
|
||||||
|
entries = entries.slice(0, 5)
|
||||||
|
}
|
||||||
|
const result = await Promise.all(
|
||||||
|
entries.map((entry) =>
|
||||||
|
collectFilesInfo(path.join(inputPath, entry), level + 1, hiddenFiles)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
files.push(...result.flat())
|
||||||
|
} catch (err) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
async function discoverFiles(argv) {
|
||||||
|
const targetPath = argv._.length < 2 ? ['.'] : argv._.slice(1)
|
||||||
|
const result = await Promise.all(
|
||||||
|
targetPath
|
||||||
|
.map((p) => path.resolve(process.cwd(), p))
|
||||||
|
.map((p) => collectFilesInfo(p, 0, argv.a || argv.all))
|
||||||
|
)
|
||||||
|
return { discover: result.flat() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = discoverFiles
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "container",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"minimist": "^1.2.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
/* eslint-disable no-restricted-syntax */
|
/* eslint-disable no-restricted-syntax */
|
||||||
const fs = require('fs').promises
|
/* eslint-disable no-await-in-loop */
|
||||||
const { exit } = require('process')
|
|
||||||
const readline = require('readline')
|
const readline = require('readline')
|
||||||
|
|
||||||
const RETURN_SYMBOL = '\u21B5'
|
const RETURN_SYMBOL = '\u21B5'
|
||||||
@@ -78,59 +76,7 @@ async function printResponses(responses, delay = 100) {
|
|||||||
rl.close()
|
rl.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readOrNone(file) {
|
module.exports = {
|
||||||
try {
|
printResponses,
|
||||||
return await fs.readFile(file, 'utf8')
|
colorize,
|
||||||
} catch (err) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const {
|
|
||||||
API_ENDPOINT,
|
|
||||||
TOKEN,
|
|
||||||
PWD,
|
|
||||||
CMD_NAME,
|
|
||||||
CMD_OUTPUT_FILE,
|
|
||||||
CMD_ERROR_FILE,
|
|
||||||
CMD_EXIT_CODE,
|
|
||||||
} = process.env
|
|
||||||
if (!CMD_NAME) exit(0)
|
|
||||||
|
|
||||||
const output = await readOrNone(CMD_OUTPUT_FILE)
|
|
||||||
const error = await readOrNone(CMD_ERROR_FILE)
|
|
||||||
const payload = {
|
|
||||||
command: CMD_NAME,
|
|
||||||
output,
|
|
||||||
error,
|
|
||||||
exit_code: CMD_EXIT_CODE,
|
|
||||||
token: TOKEN,
|
|
||||||
pwd: PWD,
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_ENDPOINT}/api/v1/commands`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
if (data.responses) {
|
|
||||||
await printResponses(data.responses, 60)
|
|
||||||
}
|
|
||||||
if (data.hints) {
|
|
||||||
for (const hint of data.hints) {
|
|
||||||
console.log(colorize(hint, 'yellow'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.end) {
|
|
||||||
console.log(colorize('Quest completed!', 'blue'))
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().then(exit).catch(console.error)
|
|
||||||
+1
-2
@@ -14,10 +14,9 @@
|
|||||||
"dockerode": "^3.3.5",
|
"dockerode": "^3.3.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"jsonwebtoken": "^9.0.1",
|
"jsonwebtoken": "^9.0.1",
|
||||||
"marked": "^5.1.1",
|
"minimist": "^1.2.8",
|
||||||
"mongoose": "^7.4.0",
|
"mongoose": "^7.4.0",
|
||||||
"socket.io": "^4.7.1",
|
"socket.io": "^4.7.1",
|
||||||
"socket.io-client": "^4.7.1",
|
|
||||||
"swagger-ui-express": "^5.0.0",
|
"swagger-ui-express": "^5.0.0",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ RUN apt-get update && apt-get install -y \
|
|||||||
zsh \
|
zsh \
|
||||||
jq
|
jq
|
||||||
|
|
||||||
|
WORKDIR /usr/local/lib/container
|
||||||
|
|
||||||
|
COPY ./packages/container/package.json ./yarn.lock ./
|
||||||
|
|
||||||
|
RUN yarn install --modules-folder /usr/src/node_modules
|
||||||
|
|
||||||
|
ENV NODE_PATH=/usr/src/node_modules
|
||||||
|
|
||||||
|
COPY ./packages/container /usr/local/lib/container
|
||||||
|
|
||||||
RUN adduser --disabled-password --gecos '' --shell /bin/zsh rudeus
|
RUN adduser --disabled-password --gecos '' --shell /bin/zsh rudeus
|
||||||
|
|
||||||
USER rudeus
|
USER rudeus
|
||||||
@@ -14,6 +24,4 @@ WORKDIR /home/rudeus
|
|||||||
|
|
||||||
COPY ./server/quests/helloworld/home /home/rudeus/
|
COPY ./server/quests/helloworld/home /home/rudeus/
|
||||||
|
|
||||||
COPY ./packages/cli/cli.js /usr/local/bin/cli.js
|
|
||||||
|
|
||||||
CMD [ "/bin/zsh" ]
|
CMD [ "/bin/zsh" ]
|
||||||
@@ -29,8 +29,14 @@ precmd() {
|
|||||||
export CMD_ERROR_FILE=/tmp/cmd.error
|
export CMD_ERROR_FILE=/tmp/cmd.error
|
||||||
|
|
||||||
# Now run your Node.js script with the result as an argument
|
# Now run your Node.js script with the result as an argument
|
||||||
node /usr/local/bin/cli.js
|
node /usr/local/lib/container/cli.js; rm /tmp/cmd.out /tmp/cmd.error 2>/dev/null
|
||||||
|
|
||||||
rm /tmp/cmd.out /tmp/cmd.error 2>/dev/null # cleanup
|
# Reset variables
|
||||||
|
unset CMD_START_TIME
|
||||||
|
unset CMD_END_TIME
|
||||||
|
unset CMD_EXIT_CODE
|
||||||
|
unset CMD_OUTPUT_FILE
|
||||||
|
unset CMD_ERROR_FILE
|
||||||
|
unset CMD_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,8 @@ import commandHandler from '../../game/commandHandler.js'
|
|||||||
|
|
||||||
export async function newCommand(req, res) {
|
export async function newCommand(req, res) {
|
||||||
console.log('new command:', req.body)
|
console.log('new command:', req.body)
|
||||||
const { token, command, pwd, output, error } = req.body
|
const { token, command, pwd, output, error, ...additionalData } = req.body
|
||||||
|
console.log('additionalData:', additionalData)
|
||||||
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
res.status(400).json({ message: 'command is required' })
|
res.status(400).json({ message: 'command is required' })
|
||||||
@@ -39,7 +40,7 @@ export async function newCommand(req, res) {
|
|||||||
|
|
||||||
await c.save()
|
await c.save()
|
||||||
|
|
||||||
const response = await commandHandler(session, c)
|
const response = await commandHandler(session, c, additionalData)
|
||||||
console.log(response)
|
console.log(response)
|
||||||
|
|
||||||
res.status(201).json(response)
|
res.status(201).json(response)
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ import { getOrCreateContainer, attachContainer } from '../utils/docker.js'
|
|||||||
import Session from '../models/session.js'
|
import Session from '../models/session.js'
|
||||||
import { defaultUser, genSessionJWT } from '../utils/auth.js'
|
import { defaultUser, genSessionJWT } from '../utils/auth.js'
|
||||||
|
|
||||||
|
const sessions = new Map()
|
||||||
|
|
||||||
|
function listenToSession(sessionId, event, callback) {
|
||||||
|
const callbacks = sessions.get((sessionId, event)) || []
|
||||||
|
sessions.set((sessionId, event), callbacks.concat(callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushToSession(sessionId, event, data) {
|
||||||
|
console.log('Push:', sessionId, event)
|
||||||
|
const callbacks = sessions.get((sessionId, event))
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach((callback) => callback(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default (server) => {
|
export default (server) => {
|
||||||
const io = new Server(server)
|
const io = new Server(server)
|
||||||
|
|
||||||
@@ -50,10 +65,12 @@ export default (server) => {
|
|||||||
const stream = await attachContainer(container, { token })
|
const stream = await attachContainer(container, { token })
|
||||||
|
|
||||||
stream.on('data', (chunk) => {
|
stream.on('data', (chunk) => {
|
||||||
socket.send(chunk.toString())
|
socket.emit('terminal', chunk.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('message', function incoming(message) {
|
socket.on('message', console.log)
|
||||||
|
|
||||||
|
socket.on('terminal', function incoming(message) {
|
||||||
stream.write(message)
|
stream.write(message)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -61,6 +78,10 @@ export default (server) => {
|
|||||||
console.log('Disconnected from the client.')
|
console.log('Disconnected from the client.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listenToSession(session.id, 'graph', (data) => {
|
||||||
|
socket.emit('graph', data)
|
||||||
|
})
|
||||||
|
|
||||||
socket.send(`${container.id}\n`)
|
socket.send(`${container.id}\n`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import minimist from 'minimist'
|
||||||
import Quest from '../models/quest.js'
|
import Quest from '../models/quest.js'
|
||||||
|
import { pushToSession } from '../api/socket.js'
|
||||||
|
|
||||||
// 檢查 pattern 是否符合 input
|
// 檢查 pattern 是否符合 input
|
||||||
const checkMatch = (pattern, input) => {
|
const checkMatch = (pattern, input) => {
|
||||||
@@ -9,7 +11,7 @@ const checkMatch = (pattern, input) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandHandler = async (session, commandInput) => {
|
async function commandHandler(session, commandInput, additionalData) {
|
||||||
const quest = await Quest.findById(session.quest)
|
const quest = await Quest.findById(session.quest)
|
||||||
const stage = quest.stages.find((s) => s.id === session.progress)
|
const stage = quest.stages.find((s) => s.id === session.progress)
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
@@ -17,6 +19,17 @@ const commandHandler = async (session, commandInput) => {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const argv = minimist(commandInput.command.split(' '))
|
||||||
|
|
||||||
|
if ('discover' in additionalData) {
|
||||||
|
pushToSession(session.id, 'graph', {
|
||||||
|
pwd: commandInput.pwd,
|
||||||
|
discover: additionalData.discover,
|
||||||
|
})
|
||||||
|
} else if (argv._[0] === 'cd') {
|
||||||
|
pushToSession(session.id, 'graph', { pwd: commandInput.pwd })
|
||||||
|
}
|
||||||
|
|
||||||
const commandMatch = checkMatch(stage.condition.command, commandInput.command)
|
const commandMatch = checkMatch(stage.condition.command, commandInput.command)
|
||||||
const outputMatch = checkMatch(stage.condition.output, commandInput.output)
|
const outputMatch = checkMatch(stage.condition.output, commandInput.output)
|
||||||
const errorMatch = checkMatch(stage.condition.error, commandInput.output)
|
const errorMatch = checkMatch(stage.condition.error, commandInput.output)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const containerOptions = {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
`${projectPath}/server/quests/helloworld/home:/home/rudeus`,
|
`${projectPath}/server/quests/helloworld/home:/home/rudeus`,
|
||||||
`${projectPath}/packages/cli/cli.js:/usr/local/bin/cli.js`,
|
`${projectPath}/packages/container:/usr/local/lib/container`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1811,14 +1811,14 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
engine.io-client@~6.5.1:
|
engine.io-client@~6.5.2:
|
||||||
version "6.5.1"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.1.tgz#1735fb8ae3bae5ae13115e18d2f484daf005dd9c"
|
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.2.tgz#8709e22c291d4297ae80318d3c8baeae71f0e002"
|
||||||
integrity sha512-hE5wKXH8Ru4L19MbM1GgYV/2Qo54JSMh1rlJbfpa40bEWkCKNo3ol2eOtGmowcr+ysgbI7+SGL+by42Q3pt/Ng==
|
integrity sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@socket.io/component-emitter" "~3.1.0"
|
"@socket.io/component-emitter" "~3.1.0"
|
||||||
debug "~4.3.1"
|
debug "~4.3.1"
|
||||||
engine.io-parser "~5.1.0"
|
engine.io-parser "~5.2.1"
|
||||||
ws "~8.11.0"
|
ws "~8.11.0"
|
||||||
xmlhttprequest-ssl "~2.0.0"
|
xmlhttprequest-ssl "~2.0.0"
|
||||||
|
|
||||||
@@ -1827,6 +1827,11 @@ engine.io-parser@~5.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.1.0.tgz#d593d6372d7f79212df48f807b8cace1ea1cb1b8"
|
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.1.0.tgz#d593d6372d7f79212df48f807b8cace1ea1cb1b8"
|
||||||
integrity sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==
|
integrity sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==
|
||||||
|
|
||||||
|
engine.io-parser@~5.2.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb"
|
||||||
|
integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==
|
||||||
|
|
||||||
engine.io@~6.5.0:
|
engine.io@~6.5.0:
|
||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.1.tgz#59725f8593ccc891abb47f1efcdc52a089525a56"
|
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.1.tgz#59725f8593ccc891abb47f1efcdc52a089525a56"
|
||||||
@@ -3432,11 +3437,6 @@ makeerror@1.0.12:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tmpl "1.0.5"
|
tmpl "1.0.5"
|
||||||
|
|
||||||
marked@^5.1.1:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-5.1.1.tgz#40b3963bb9da225314f746d5012ba7e34942f636"
|
|
||||||
integrity sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg==
|
|
||||||
|
|
||||||
media-typer@0.3.0:
|
media-typer@0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
@@ -3509,7 +3509,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.6:
|
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
@@ -4269,14 +4269,14 @@ socket.io-adapter@~2.5.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ws "~8.11.0"
|
ws "~8.11.0"
|
||||||
|
|
||||||
socket.io-client@^4.7.1:
|
socket.io-client@^4.7.2:
|
||||||
version "4.7.1"
|
version "4.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.1.tgz#48e5f703abe4fb0402182bcf9c06b7820fb3453b"
|
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.2.tgz#f2f13f68058bd4e40f94f2a1541f275157ff2c08"
|
||||||
integrity sha512-Qk3Xj8ekbnzKu3faejo4wk2MzXA029XppiXtTF/PkbTg+fcwaTw1PlDrTrrrU4mKoYC4dvlApOnSeyLCKwek2w==
|
integrity sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@socket.io/component-emitter" "~3.1.0"
|
"@socket.io/component-emitter" "~3.1.0"
|
||||||
debug "~4.3.2"
|
debug "~4.3.2"
|
||||||
engine.io-client "~6.5.1"
|
engine.io-client "~6.5.2"
|
||||||
socket.io-parser "~4.2.4"
|
socket.io-parser "~4.2.4"
|
||||||
|
|
||||||
socket.io-parser@~4.2.4:
|
socket.io-parser@~4.2.4:
|
||||||
|
|||||||
Reference in new issue
Block a user