Add minimal frontend
This commit is contained in:
parent
b2a04ba399
commit
041e1ccda6
|
@ -14,14 +14,14 @@ export default (sessions) => {
|
||||||
cors(app)
|
cors(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get('/containers', async (req, res) => {
|
app.get('/api/containers', async (req, res) => {
|
||||||
return res.json(
|
return res.json(
|
||||||
(await runningContainers())
|
(await runningContainers())
|
||||||
.map(c => ({ id: c.Id, image: c.Image, labels: c.Labels }))
|
.map(c => ({ id: c.Id, image: c.Image, labels: c.Labels }))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post('/containers', async (req, res) => {
|
app.post('/api/containers', async (req, res) => {
|
||||||
if ((req.body.image && typeof req.body.image !== 'string') || (req.body.cmd && Array.isArray(req.body.cmd))) {
|
if ((req.body.image && typeof req.body.image !== 'string') || (req.body.cmd && Array.isArray(req.body.cmd))) {
|
||||||
return res.send('invalid arguments').status(401)
|
return res.send('invalid arguments').status(401)
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ export default (sessions) => {
|
||||||
res.send(container)
|
res.send(container)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post('/containers/:container/:session/resize', (req, res) => {
|
app.post('/api/containers/:container/sessions/:session/resize', (req, res) => {
|
||||||
const session = sessions[req.params.session]
|
const session = sessions[req.params.session]
|
||||||
|
|
||||||
if (!session || session.container !== req.params.container) {
|
if (!session || session.container !== req.params.container) {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
|
"vue-router": "^4.0.0-0",
|
||||||
"xterm": "^4.14.1",
|
"xterm": "^4.14.1",
|
||||||
"xterm-addon-attach": "^0.6.0",
|
"xterm-addon-attach": "^0.6.0",
|
||||||
"xterm-addon-fit": "^0.5.0"
|
"xterm-addon-fit": "^0.5.0"
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.0",
|
||||||
"@vue/compiler-sfc": "^3.0.0",
|
"@vue/compiler-sfc": "^3.0.0",
|
||||||
"@vue/eslint-config-standard": "^5.1.2",
|
"@vue/eslint-config-standard": "^5.1.2",
|
||||||
|
|
|
@ -1,14 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<WsTerm container="fc4854871b5d9ad97f8f8c5daec81e5452bd1bb59fb50ea5cbcdac6787600d53" session="random-string"/>
|
<div id="nav">
|
||||||
|
<router-link to="/">Home</router-link> |
|
||||||
|
</div>
|
||||||
|
<router-view/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<style>
|
||||||
import WsTerm from './components/WsTerm.vue'
|
#app {
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
export default {
|
-webkit-font-smoothing: antialiased;
|
||||||
name: 'App',
|
-moz-osx-font-smoothing: grayscale;
|
||||||
components: {
|
text-align: center;
|
||||||
WsTerm
|
color: #2c3e50;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
#nav {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav a.router-link-exact-active {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>Containers</h2>
|
||||||
|
<ul>
|
||||||
|
<li v-for="container in containers" :key="container.id">
|
||||||
|
{{ container.id.substr(0, 8) }}
|
||||||
|
|
||||||
|
<router-link :to="{ name: 'new-session', params: { container: container.id } }">
|
||||||
|
Start session
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ContainerList',
|
||||||
|
props: {
|
||||||
|
containers: Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -8,6 +8,7 @@ import { Terminal } from 'xterm'
|
||||||
import { AttachAddon } from 'xterm-addon-attach'
|
import { AttachAddon } from 'xterm-addon-attach'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { FitAddon } from 'xterm-addon-fit'
|
import { FitAddon } from 'xterm-addon-fit'
|
||||||
|
import { resizeTerminal } from '../lib/api.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WsTerm',
|
name: 'WsTerm',
|
||||||
|
@ -31,14 +32,7 @@ export default {
|
||||||
const resize = () => {
|
const resize = () => {
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
|
|
||||||
fetch(`http://localhost:1234/containers/${props.container}/${props.session}/resize`, {
|
resizeTerminal(props.container, props.session, term.cols, term.rows)
|
||||||
method: 'post',
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
cols: term.cols,
|
|
||||||
rows: term.rows
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
const API_URL = 'http://localhost:1234'
|
||||||
|
|
||||||
|
export async function getJson (url) {
|
||||||
|
return (await fetch(url)).json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postJson (url, data) {
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'post',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContainers () {
|
||||||
|
return getJson(`${API_URL}/api/containers`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resizeTerminal (containerId, sessionId, cols, rows) {
|
||||||
|
return postJson(`${API_URL}/api/containers/${containerId}/sessions/${sessionId}/resize`, {
|
||||||
|
cols, rows
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
const randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
|
||||||
|
export function randomString (length = 20) {
|
||||||
|
const values = new Uint32Array(length)
|
||||||
|
window.crypto.getRandomValues(values)
|
||||||
|
|
||||||
|
return Array.from(values.map(v => v % randomChars.length)).map(c => randomChars[c]).join('')
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
createApp(App)
|
||||||
|
.use(router)
|
||||||
|
.mount('#app')
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import Home from '../views/Dashboard.vue'
|
||||||
|
import { randomString } from '../lib/helpers'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/container/:container/session',
|
||||||
|
name: 'new-session',
|
||||||
|
redirect: to => {
|
||||||
|
const container = to.params.container
|
||||||
|
let session = localStorage.getItem(container)
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
session = randomString()
|
||||||
|
localStorage.setItem(container, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: 'session', params: { container, session } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/container/:container/session/:session',
|
||||||
|
name: 'session',
|
||||||
|
component: () => import('../views/Session.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<ContainerList :containers="containers"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ContainerList from '../components/ContainerList'
|
||||||
|
import { getContainers } from '../lib/api'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Dashboard',
|
||||||
|
components: { ContainerList },
|
||||||
|
setup () {
|
||||||
|
const containers = ref([])
|
||||||
|
|
||||||
|
getContainers()
|
||||||
|
.then(c => {
|
||||||
|
containers.value = c
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
containers: containers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<WsTerm :container="container" :session="session"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WsTerm from '../components/WsTerm'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { WsTerm },
|
||||||
|
|
||||||
|
setup () {
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
return {
|
||||||
|
container: route.params.container,
|
||||||
|
session: route.params.session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1296,7 +1296,7 @@
|
||||||
webpack "^4.0.0"
|
webpack "^4.0.0"
|
||||||
yorkie "^2.0.0"
|
yorkie "^2.0.0"
|
||||||
|
|
||||||
"@vue/cli-plugin-router@^4.5.15":
|
"@vue/cli-plugin-router@^4.5.15", "@vue/cli-plugin-router@~4.5.0":
|
||||||
version "4.5.15"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
|
||||||
integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
|
integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
|
||||||
|
@ -1447,6 +1447,11 @@
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
prettier "^1.18.2 || ^2.0.0"
|
prettier "^1.18.2 || ^2.0.0"
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.0.0-beta.18":
|
||||||
|
version "6.0.0-beta.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.19.tgz#f8e88059daa424515992426a0c7ea5cde07e99bf"
|
||||||
|
integrity sha512-ObzQhgkoVeoyKv+e8+tB/jQBL2smtk/NmC9OmFK8UqdDpoOdv/Kf9pyDWL+IFyM7qLD2C75rszJujvGSPSpGlw==
|
||||||
|
|
||||||
"@vue/eslint-config-standard@^5.1.2":
|
"@vue/eslint-config-standard@^5.1.2":
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/eslint-config-standard/-/eslint-config-standard-5.1.2.tgz#c5d55af894a3ae23b65b1af4a425777ac0170b42"
|
resolved "https://registry.yarnpkg.com/@vue/eslint-config-standard/-/eslint-config-standard-5.1.2.tgz#c5d55af894a3ae23b65b1af4a425777ac0170b42"
|
||||||
|
@ -8546,6 +8551,13 @@ vue-loader@^15.9.2:
|
||||||
vue-hot-reload-api "^2.3.0"
|
vue-hot-reload-api "^2.3.0"
|
||||||
vue-style-loader "^4.1.0"
|
vue-style-loader "^4.1.0"
|
||||||
|
|
||||||
|
vue-router@^4.0.0-0:
|
||||||
|
version "4.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.12.tgz#8dc792cddf5bb1abcc3908f9064136de7e13c460"
|
||||||
|
integrity sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^6.0.0-beta.18"
|
||||||
|
|
||||||
vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
|
vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
|
||||||
version "4.1.3"
|
version "4.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
||||||
|
|
Loading…
Reference in New Issue