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>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								frontend/src/components/ContainerList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/src/components/ContainerList.vue
									
									
									
									
									
										Normal file
									
								
							@ -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(() => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								frontend/src/lib/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/src/lib/api.js
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								frontend/src/lib/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/lib/helpers.js
									
									
									
									
									
										Normal file
									
								
							@ -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')
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								frontend/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
							
								
								
									
										28
									
								
								frontend/src/views/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/src/views/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							@ -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>
 | 
				
			||||||
							
								
								
									
										23
									
								
								frontend/src/views/Session.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/src/views/Session.vue
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user