├── site ├── public │ ├── favicon.ico │ ├── list.json │ ├── vercel.svg │ └── logo.svg ├── postcss.config.js ├── components │ ├── footer.js │ ├── Bubbles.js │ ├── layout.js │ ├── Workspace.js │ └── header.js ├── tailwind.config.js ├── pages │ ├── _app.js │ ├── index.js │ └── new │ │ └── [[...workspace]].js ├── next.config.js ├── package.json ├── README.md ├── styles │ ├── Home.module.css │ └── globals.css └── yarn.lock ├── workspaces ├── firefox │ ├── firefox.png │ └── workspace.json ├── chromium │ ├── chromium.png │ └── workspace.json └── README.md ├── processing ├── package.json ├── update_1_0_to_1_1.js ├── add_next_version.js ├── get_image_sizes.js ├── processjson.js └── package-lock.json ├── .github └── workflows │ └── build-and-deploy.yml ├── .gitignore └── README.md /site/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/kasm_workspaces_registry/HEAD/site/public/favicon.ico -------------------------------------------------------------------------------- /site/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /workspaces/firefox/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/kasm_workspaces_registry/HEAD/workspaces/firefox/firefox.png -------------------------------------------------------------------------------- /workspaces/chromium/chromium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/kasm_workspaces_registry/HEAD/workspaces/chromium/chromium.png -------------------------------------------------------------------------------- /workspaces/README.md: -------------------------------------------------------------------------------- 1 | # Workspaces directory 2 | 3 | This directory is for storing all the files needed for your workspaces store, they should be stored with the following structure: 4 | 5 | * workspaces/Workspace Name 6 | * workspaces/Workspace Name/workspace.json 7 | * workspaces/Workspace Name/workspace-name.png -------------------------------------------------------------------------------- /processing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kasm-Apps", 3 | "version": "1.0.0", 4 | "description": "Generate json file", 5 | "main": "processjson.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "bugs": {}, 13 | "devDependencies": { 14 | "folder-hash": "^4.0.2", 15 | "glob": "^7.1.6" 16 | } 17 | } -------------------------------------------------------------------------------- /site/components/footer.js: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 | 6 | ) 7 | } -------------------------------------------------------------------------------- /site/components/Bubbles.js: -------------------------------------------------------------------------------- 1 | function Bubbles() { 2 | return ( 3 | 15 | ) 16 | } 17 | 18 | export default Bubbles 19 | -------------------------------------------------------------------------------- /site/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | app: { 11 | 900: '#5f4c7c', 12 | 800: '#9178bd', 13 | 700: '#b199da' 14 | } 15 | } 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | -------------------------------------------------------------------------------- /site/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import Layout from '../components/layout' 3 | import { useState, useEffect } from 'react' 4 | 5 | 6 | function MyApp({ Component, pageProps }) { 7 | const [searchText, setSearchText] = useState('') 8 | 9 | const changeSearch = event => { 10 | setSearchText(event.target.value) 11 | } 12 | 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | export default MyApp 21 | -------------------------------------------------------------------------------- /site/components/layout.js: -------------------------------------------------------------------------------- 1 | // components/layout.js 2 | 3 | import Header from './header' 4 | import Footer from './footer' 5 | import 'react-notifications/lib/notifications.css'; 6 | import { NotificationContainer } from 'react-notifications'; 7 | 8 | export default function Layout({ children, searchText, changeSearch }) { 9 | return ( 10 |
11 |
12 |
{children}
13 |
16 | ) 17 | } -------------------------------------------------------------------------------- /site/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | output: 'export', 5 | distDir: '../public', 6 | env: { 7 | name: 'LinuxServer.io', 8 | description: 'Kasm workspaces provided by LSIO expertise', 9 | icon: 'https://www.linuxserver.io/user/assets/typhoon/Asset%202.svg', 10 | listUrl: 'https://kasmregistry.linuxserver.io/', 11 | contactUrl: 'https://discord.gg/YWrKVTn', 12 | }, 13 | reactStrictMode: true, 14 | basePath: '/1.0', 15 | assetPrefix: '/1.0', 16 | trailingSlash: true, 17 | images: { 18 | unoptimized: true, 19 | } 20 | } 21 | 22 | module.exports = nextConfig 23 | -------------------------------------------------------------------------------- /site/public/list.json: -------------------------------------------------------------------------------- 1 | {"workspacecount":1,"workspaces":[{"name":"Chromium","icon":"chromium.png","description":"Chromium is a free and open-source browser, primarily developed and maintained by Google.","image":"kasmweb/chromium:develop","cores":2,"memory":2768,"gpu_count":0,"cpu_allocation":"inherit","docker_registry":"https://index.docker.io/v1/","volume_mappings":{},"config_override":{"hostname":"kasm"},"exec_config":{"go":{"cmd":"bash -c '/dockerstartup/custom_startup.sh --go --url \"$KASM_URL\"'"},"assign":{"cmd":"bash -c '/dockerstartup/custom_startup.sh --assign --url \"$KASM_URL\"'"}},"categories":["Browser"],"sha":"13126dde5f5338398a728debe459dd1106548aef"}]} -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'gh-pages' 7 | permissions: 8 | contents: write 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Build # Have to run processing first so the list.json exists to be included in the the deploy 17 | run: | 18 | npm ci --prefix processing 19 | npm ci --prefix site 20 | ./build_all_branches.sh 21 | 22 | - name: Deploy 23 | uses: JamesIves/github-pages-deploy-action@v4 24 | with: 25 | branch: gh-pages 26 | folder: public 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist 3 | public/* 4 | .next 5 | target 6 | packages/next/wasm/@next 7 | out 8 | site/public/icons 9 | site/public/list.json 10 | !site/public/logo.svg 11 | 12 | # dependencies 13 | node_modules 14 | 15 | # logs & pids 16 | *.log 17 | pids 18 | *.cpuprofile 19 | 20 | # coverage 21 | .nyc_output 22 | coverage 23 | 24 | # test output 25 | test/**/out* 26 | test/**/next-env.d.ts 27 | .DS_Store 28 | /e2e-tests 29 | test/tmp/** 30 | 31 | # Editors 32 | **/.idea 33 | **/.#* 34 | .nvmrc 35 | 36 | # examples 37 | examples/**/out 38 | examples/**/.env*.local 39 | 40 | pr-stats.md 41 | test-timings.json 42 | 43 | # Vercel 44 | .vercel 45 | .now 46 | 47 | # Cache 48 | *.tsbuildinfo 49 | .swc/ -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "deploy": "next build && touch ../public/.nojekyll", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "file-saver": "^2.0.5", 13 | "jszip": "^3.10.1", 14 | "next": "^14.2.27", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "react-notifications": "^1.7.4", 18 | "react-select": "^5.6.1" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^10.4.13", 22 | "eslint": "8.26.0", 23 | "eslint-config-next": "13.0.0", 24 | "postcss": "^8.4.18", 25 | "tailwindcss": "^3.2.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /processing/update_1_0_to_1_1.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const glob = require("glob"); 3 | 4 | glob("../workspaces/**/workspace.json", async function (err, files) { 5 | if (err) { 6 | console.log( 7 | "cannot read the folder, something goes wrong with glob", 8 | err 9 | ); 10 | } 11 | 12 | 13 | for (const file of files) { 14 | 15 | let filedata = fs.readFileSync(file); 16 | let parsed = JSON.parse(filedata); 17 | delete parsed.compatibility 18 | 19 | parsed.compatibility = [] 20 | 21 | let details = { 22 | version: '1.16.x', 23 | image: parsed.name, 24 | uncompressed_size_mb: parsed.uncompressed_size_mb 25 | } 26 | 27 | parsed.compatibility.push(details) 28 | delete parsed.uncompressed_size_mb 29 | delete parsed.name 30 | 31 | fs.writeFileSync(file, JSON.stringify(parsed, null, 2)); 32 | } 33 | 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /site/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /processing/add_next_version.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const glob = require("glob"); 3 | 4 | const baseversion = '1.18' 5 | const tag = ':develop' 6 | 7 | const version = baseversion + '.x' 8 | const tagversion = baseversion + '.0' 9 | 10 | glob("../workspaces/**/workspace.json", async function (err, files) { 11 | if (err) { 12 | console.log( 13 | "cannot read the folder, something goes wrong with glob", 14 | err 15 | ); 16 | } 17 | 18 | 19 | for (const file of files) { 20 | 21 | let filedata = fs.readFileSync(file); 22 | let parsed = JSON.parse(filedata); 23 | 24 | const current = parsed.compatibility[parsed.compatibility.length - 1] 25 | const image = current.image.split(':')[0] 26 | 27 | const exists = parsed.compatibility.findIndex(el => el.version === version) 28 | 29 | let details = { 30 | version, 31 | image: current.image, 32 | uncompressed_size_mb: current.uncompressed_size_mb, 33 | } 34 | 35 | if (exists === -1) { 36 | parsed.compatibility.push(details) 37 | fs.writeFileSync(file, JSON.stringify(parsed, null, 2)); 38 | } 39 | } 40 | 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /workspaces/firefox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Firefox, is a free and open-source web browser developed by the Mozilla Foundation and its subsidiary, the Mozilla Corporation.", 3 | "docker_registry": "https://ghcr.io/", 4 | "image_src": "firefox.png", 5 | "run_config": { 6 | "security_opt": [ 7 | "seccomp=unconfined" 8 | ], 9 | "entrypoint": [ 10 | "/kasminit" 11 | ], 12 | "user": 1000, 13 | "environment": { 14 | "HOME": "/home/kasm-user" 15 | } 16 | }, 17 | "exec_config": { 18 | "go": { 19 | "cmd": "bash -c 'HOME=/home/kasm-user firefox \"$KASM_URL\"'" 20 | }, 21 | "assign": { 22 | "cmd": "bash -c 'HOME=/home/kasm-user firefox \"$KASM_URL\"'" 23 | } 24 | }, 25 | "categories": [ 26 | "Browser" 27 | ], 28 | "friendly_name": "Firefox", 29 | "architecture": [ 30 | "amd64", 31 | "arm64" 32 | ], 33 | "compatibility": [ 34 | { 35 | "version": "1.16.x", 36 | "image": "lscr.io/linuxserver/firefox:kasm", 37 | "uncompressed_size_mb": 1230 38 | }, 39 | { 40 | "version": "1.17.x", 41 | "image": "lscr.io/linuxserver/firefox:kasm", 42 | "uncompressed_size_mb": 1230 43 | }, 44 | { 45 | "version": "1.18.x", 46 | "image": "lscr.io/linuxserver/firefox:kasm", 47 | "uncompressed_size_mb": 1230 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /workspaces/chromium/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all users to experience the web.", 3 | "docker_registry": "https://ghcr.io/", 4 | "image_src": "chromium.png", 5 | "run_config": { 6 | "security_opt": [ 7 | "seccomp=unconfined" 8 | ], 9 | "entrypoint": [ 10 | "/kasminit" 11 | ], 12 | "user": 1000, 13 | "environment": { 14 | "HOME": "/home/kasm-user" 15 | } 16 | }, 17 | "exec_config": { 18 | "go": { 19 | "cmd": "bash -c 'HOME=/home/kasm-user wrapped-chromium \"$KASM_URL\"'" 20 | }, 21 | "assign": { 22 | "cmd": "bash -c 'HOME=/home/kasm-user wrapped-chromium \"$KASM_URL\"'" 23 | } 24 | }, 25 | "categories": [ 26 | "Browser" 27 | ], 28 | "friendly_name": "Chromium", 29 | "architecture": [ 30 | "amd64", 31 | "arm64" 32 | ], 33 | "compatibility": [ 34 | { 35 | "version": "1.16.x", 36 | "image": "lscr.io/linuxserver/chromium:kasm", 37 | "uncompressed_size_mb": 1250 38 | }, 39 | { 40 | "version": "1.17.x", 41 | "image": "lscr.io/linuxserver/chromium:kasm", 42 | "uncompressed_size_mb": 1250 43 | }, 44 | { 45 | "version": "1.18.x", 46 | "image": "lscr.io/linuxserver/chromium:kasm", 47 | "uncompressed_size_mb": 1250 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /processing/get_image_sizes.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const glob = require("glob"); 3 | const { execSync } = require('child_process'); 4 | 5 | 6 | glob("../workspaces/**/workspace.json", function (err, files) { 7 | if (err) { 8 | console.log( 9 | "cannot read the folder, something goes wrong with glob", 10 | err 11 | ); 12 | } 13 | 14 | let total = 0 15 | for (const file of files) { 16 | 17 | let filedata = fs.readFileSync(file); 18 | let parsed = JSON.parse(filedata); 19 | 20 | 21 | parsed.compatibility.forEach((element, index) => { 22 | total++ 23 | if (element.uncompressed_size_mb === 0) { 24 | execSync('docker image prune -a -f') 25 | execSync('docker system prune --all --force --volumes') 26 | 27 | let pull = execSync('docker pull ' + element.image) 28 | // console.log(pull) 29 | let inspect = execSync('docker inspect -f "{{ .Size }}" ' + element.image) 30 | let size = Math.round(inspect / 1000000) 31 | let remove = execSync('docker rmi ' + element.image) 32 | console.log(remove) 33 | parsed.compatibility[index].uncompressed_size_mb = size 34 | console.log('Write file: ' + parsed.friendly_name + ' - ' + element.version + ': ' + size) 35 | fs.writeFileSync(file, JSON.stringify(parsed, null, 2)); 36 | } else { 37 | console.log(parsed.friendly_name + ' - ' + element.version + ': skipped') 38 | } 39 | 40 | }) 41 | 42 | } 43 | console.log(total + ' entries processed') 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /site/components/Workspace.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | 3 | function Workspace({ Component, pageProps, workspace }) { 4 | const router = useRouter() 5 | 6 | const viewexample = (workspace) => { 7 | router.push({ 8 | pathname: '/new/[workspace]', 9 | query: { workspace: btoa(workspace.friendly_name)} 10 | }) 11 | } 12 | 13 | return ( 14 |
viewexample(workspace)} className="w-[245px] h-[88px] transition-all relative cursor-pointer group flex p-2 items-center justify-center bg-slate-100/90 shadow rounded hover:shadow-xl hover:bg-gradient-to-r hover:from-[#5b2e91] hover:to-[#ff4f97] hover:text-white"> 15 |
16 |
17 |
18 | 19 |
20 |
21 |
{ workspace.friendly_name }
22 |

{ workspace.categories && workspace.categories[0] || 'Unknown' }

23 |
24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default Workspace -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /processing/processjson.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const glob = require("glob"); 3 | const { hashElement } = require("folder-hash"); 4 | const nextConfig = require("../site/next.config.js") 5 | 6 | var dir = "./public"; 7 | 8 | if (!fs.existsSync(dir)) { 9 | fs.mkdirSync(dir); 10 | } 11 | if (!fs.existsSync(dir + "/icons")) { 12 | fs.mkdirSync(dir + "/icons"); 13 | } 14 | 15 | glob("**/workspace.json", async function (err, files) { 16 | if (err) { 17 | console.log( 18 | "cannot read the folder, something goes wrong with glob", 19 | err 20 | ); 21 | } 22 | 23 | let workspacetotal = files.length; 24 | let workspaces = []; 25 | let promises = []; 26 | 27 | const options = { 28 | algho: "sha1", 29 | encoding: "hex", 30 | }; 31 | 32 | let channels = new Set() 33 | let versions = new Set() 34 | 35 | for (const file of files) { 36 | //files.forEach(async function(file) { 37 | 38 | let folder = file.replace("/workspace.json", ""); 39 | 40 | let hash = await hashElement(folder, options); 41 | let filedata = fs.readFileSync(file); 42 | 43 | let parsed = JSON.parse(filedata); 44 | parsed.sha = hash.hash; 45 | console.log(parsed.friendly_name + ' added') 46 | parsed.compatibility.forEach((element, index) => { 47 | if ('available_tags' in element) { 48 | element.available_tags.forEach((el) => { 49 | channels.add(el) 50 | }) 51 | } 52 | if ('version' in element) { 53 | versions.add(element.version) 54 | } 55 | }) 56 | workspaces.push(parsed); 57 | 58 | if (fs.existsSync(folder + "/" + parsed.image_src)) { 59 | let imagedata = fs.readFileSync(folder + "/" + parsed.image_src); 60 | fs.writeFileSync(dir + "/icons/" + parsed.image_src, imagedata); 61 | } else { 62 | console.error("missing file: ".folder + "/" + parsed.image_src); 63 | } 64 | 65 | } 66 | 67 | let json = { 68 | name: nextConfig.env.name || 'Unknown store', 69 | workspacecount: workspacetotal, 70 | icon: nextConfig.env.icon || null, 71 | description: nextConfig.env.description || null, 72 | list_url: nextConfig.env.listUrl || null, 73 | contact_url: nextConfig.env.contactUrl || null, 74 | modified: Date.now(), 75 | workspaces: workspaces, 76 | channels: [...channels], 77 | default_channel: 'develop' 78 | }; 79 | 80 | if (channels.size === 0) { 81 | json.default_channel = null 82 | } 83 | 84 | let data = JSON.stringify(json); 85 | 86 | fs.writeFileSync(dir + "/list.json", data); 87 | fs.writeFileSync(dir + "/versions.json", JSON.stringify({ 88 | versions: [...versions] 89 | })); 90 | }); 91 | -------------------------------------------------------------------------------- /site/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | .card, 120 | .footer { 121 | border-color: #222; 122 | } 123 | .code { 124 | background: #111; 125 | } 126 | .logo img { 127 | filter: invert(1); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /site/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | body { 8 | @apply bg-gradient-to-tr from-slate-300 to-slate-300 min-h-screen text-slate-700; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 10 | font-weight: 400; 11 | } 12 | 13 | .notification { 14 | box-shadow: none!important; 15 | } 16 | 17 | .bg-bubbles { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 100%; 23 | z-index: 1; 24 | } 25 | .bg-bubbles li { 26 | position: absolute; 27 | list-style: none; 28 | display: block; 29 | border: 1px solid rgba(255,255,255,0.1); 30 | width: 40px; 31 | height: 40px; 32 | background-color: rgba(255, 255, 255, 0.05); 33 | bottom: -160px; 34 | -webkit-animation: square 25s infinite; 35 | animation: square 25s infinite; 36 | transition-timing-function: linear; 37 | } 38 | .bg-bubbles li:nth-child(1) { 39 | left: 10%; 40 | bottom: -60px; 41 | } 42 | .bg-bubbles li:nth-child(2) { 43 | left: 20%; 44 | width: 80px; 45 | height: 80px; 46 | -webkit-animation-delay: 2s; 47 | animation-delay: 2s; 48 | -webkit-animation-duration: 17s; 49 | animation-duration: 17s; 50 | } 51 | .bg-bubbles li:nth-child(3) { 52 | left: 25%; 53 | -webkit-animation-delay: 4s; 54 | animation-delay: 4s; 55 | } 56 | .bg-bubbles li:nth-child(4) { 57 | left: 40%; 58 | width: 60px; 59 | height: 60px; 60 | -webkit-animation-duration: 22s; 61 | animation-duration: 22s; 62 | background-color: rgba(255, 255, 255, 0.15); 63 | border: 1px solid rgba(255,255,255,0.2); 64 | } 65 | .bg-bubbles li:nth-child(5) { 66 | left: 70%; 67 | bottom: -20px; 68 | } 69 | .bg-bubbles li:nth-child(6) { 70 | left: 80%; 71 | width: 120px; 72 | height: 120px; 73 | -webkit-animation-delay: 3s; 74 | animation-delay: 3s; 75 | background-color: rgba(255, 255, 255, 0.1); 76 | border: 1px solid rgba(255,255,255,0.15); 77 | } 78 | .bg-bubbles li:nth-child(7) { 79 | left: 32%; 80 | width: 160px; 81 | height: 160px; 82 | -webkit-animation-delay: 7s; 83 | animation-delay: 7s; 84 | } 85 | .bg-bubbles li:nth-child(8) { 86 | left: 55%; 87 | width: 20px; 88 | height: 20px; 89 | -webkit-animation-delay: 15s; 90 | animation-delay: 15s; 91 | -webkit-animation-duration: 40s; 92 | animation-duration: 40s; 93 | } 94 | .bg-bubbles li:nth-child(9) { 95 | left: 25%; 96 | width: 10px; 97 | height: 10px; 98 | -webkit-animation-delay: 2s; 99 | animation-delay: 2s; 100 | -webkit-animation-duration: 40s; 101 | animation-duration: 40s; 102 | background-color: rgba(255, 255, 255, 0.17); 103 | border: 1px solid rgba(255,255,255,0.22); 104 | } 105 | .bg-bubbles li:nth-child(10) { 106 | left: 90%; 107 | width: 160px; 108 | height: 160px; 109 | -webkit-animation-delay: 11s; 110 | animation-delay: 11s; 111 | } 112 | @-webkit-keyframes square { 113 | 0% { 114 | transform: translateY(0); 115 | } 116 | 100% { 117 | transform: translateY(-400px) rotate(600deg); 118 | } 119 | } 120 | @keyframes square { 121 | 0% { 122 | transform: translateY(0); 123 | } 124 | 100% { 125 | transform: translateY(-400px) rotate(600deg); 126 | } 127 | } -------------------------------------------------------------------------------- /site/components/header.js: -------------------------------------------------------------------------------- 1 | import Bubbles from '../components/Bubbles' 2 | import Link from 'next/link' 3 | import { useRouter } from "next/router"; 4 | import { NotificationManager } from 'react-notifications'; 5 | import Image from 'next/image' 6 | 7 | export default function Header({ searchText, changeSearch }) { 8 | 9 | const copyToClipboard = () => { 10 | var textField = document.createElement('textarea') 11 | textField.innerText = listUrl 12 | document.body.appendChild(textField) 13 | textField.select() 14 | document.execCommand('copy') 15 | textField.remove() 16 | NotificationManager.info('URL successfully copied to clipboard', 'Copy URL', 4000); 17 | } 18 | const listUrl = process.env.listUrl; 19 | const router = useRouter(); 20 | 21 | return ( 22 |
23 | 24 |
25 |
26 | LinuxServer.io 27 |
28 |
29 | W 30 | o 31 | r 32 | k 33 | s 34 | p 35 | a 36 | c 37 | e 38 |   39 | R 40 | e 41 | g 42 | i 43 | s 44 | t 45 | r 46 | y 47 |
48 |
49 | 53 |
54 |
55 | 63 | 64 |
65 | 66 |
67 | 71 |
72 | 73 | ) 74 | } -------------------------------------------------------------------------------- /site/pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import Head from 'next/head' 3 | import Workspace from '../components/Workspace' 4 | import styles from '../styles/Home.module.css' 5 | 6 | export default function Home({ searchText }) { 7 | 8 | const [workspaces, setWorkspaces] = useState(null) 9 | const [versions, setVersions] = useState(null) 10 | const [version, setVersion] = useState(null) 11 | 12 | useEffect(() => { 13 | let currentVersion = localStorage.getItem("version") || null 14 | fetch('list.json') 15 | .then((res) => res.json()) 16 | .then((workspaces) => { 17 | let wsversions = [] 18 | workspaces.workspaces.forEach((workspace) => { 19 | if(workspace.compatibility) { 20 | workspace.compatibility.forEach((v) => { 21 | const value = parseFloat(v.version) 22 | if(wsversions.indexOf(value) === -1) { 23 | wsversions.push(value) 24 | } 25 | }) 26 | } 27 | }) 28 | const sorted = wsversions.sort((a,b) => a-b).reverse() 29 | 30 | setVersions(sorted) 31 | if (currentVersion === null) { 32 | currentVersion = sorted[0] 33 | localStorage.setItem("version", currentVersion); 34 | } 35 | setVersion(currentVersion) 36 | setWorkspaces(workspaces) 37 | }) 38 | }, []) 39 | 40 | const updateVersion = (version) => { 41 | localStorage.setItem("version", version); 42 | setVersion(version) 43 | } 44 | 45 | let filteredworkspaces = workspaces && workspaces.workspaces && workspaces.workspaces.length > 0 ? [...workspaces.workspaces] : []; 46 | filteredworkspaces = filteredworkspaces.filter((v) => v.compatibility.some((el) => el.version === version + '.x')) 47 | const lowerSearch = searchText && searchText.toLowerCase(); 48 | if (searchText && searchText !== "") { 49 | filteredworkspaces = filteredworkspaces.filter((i) => { 50 | const category = (i.categories && i.categories.length > 0) ? i.categories.filter((i) => 51 | i.toLowerCase().includes(lowerSearch) 52 | ) : []; 53 | return ( 54 | i.name.toLowerCase().includes(lowerSearch) || 55 | category.length > 0 56 | ); 57 | }); 58 | } 59 | 60 | 61 | return ( 62 |
63 | 64 | Kasm Workspaces 65 | 66 | 67 | 68 | 69 | 70 |
71 |

72 | 73 | Workspaces 74 | {filteredworkspaces && filteredworkspaces.length} 75 | 76 | 77 | Kasm Version 78 | {versions && versions.map((v) => ( 79 |
updateVersion(v)}>{v}
80 | ))}
81 |
82 |

83 |
84 | {filteredworkspaces && filteredworkspaces.length > 0 && filteredworkspaces.map(function (workspace, i) { 85 | return 86 | })} 87 | {filteredworkspaces && filteredworkspaces.length === 0 && ( 88 |

No workspaces found {searchText !== '' && ('matching "' + searchText + '"')}

89 | )} 90 |
91 | 92 | 93 |
94 | 95 |
96 |
97 |
98 | ) 99 | } 100 | -------------------------------------------------------------------------------- /site/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /processing/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kasm-Apps", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "Kasm-Apps", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "folder-hash": "^4.0.2", 13 | "glob": "^7.1.6" 14 | } 15 | }, 16 | "node_modules/balanced-match": { 17 | "version": "1.0.2", 18 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 19 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 20 | "dev": true 21 | }, 22 | "node_modules/brace-expansion": { 23 | "version": "2.0.1", 24 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 25 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 26 | "dev": true, 27 | "dependencies": { 28 | "balanced-match": "^1.0.0" 29 | } 30 | }, 31 | "node_modules/concat-map": { 32 | "version": "0.0.1", 33 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 34 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 35 | "dev": true 36 | }, 37 | "node_modules/debug": { 38 | "version": "4.3.4", 39 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 40 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 41 | "dev": true, 42 | "dependencies": { 43 | "ms": "2.1.2" 44 | }, 45 | "engines": { 46 | "node": ">=6.0" 47 | }, 48 | "peerDependenciesMeta": { 49 | "supports-color": { 50 | "optional": true 51 | } 52 | } 53 | }, 54 | "node_modules/folder-hash": { 55 | "version": "4.0.2", 56 | "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-4.0.2.tgz", 57 | "integrity": "sha512-Iw9GCqdA+zHfDVvk90TSAV66jq0IwiZaPvPgUiW+DHRwnaPOeZomzlgutx9QclinsQGz/XcVIGlDEJbFhCV5wA==", 58 | "dev": true, 59 | "dependencies": { 60 | "debug": "^4.3.3", 61 | "graceful-fs": "~4.2.9", 62 | "minimatch": "~5.0.0" 63 | }, 64 | "bin": { 65 | "folder-hash": "bin/folder-hash" 66 | }, 67 | "engines": { 68 | "node": ">=10.10.0" 69 | } 70 | }, 71 | "node_modules/fs.realpath": { 72 | "version": "1.0.0", 73 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 74 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 75 | "dev": true 76 | }, 77 | "node_modules/glob": { 78 | "version": "7.2.3", 79 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 80 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 81 | "dev": true, 82 | "dependencies": { 83 | "fs.realpath": "^1.0.0", 84 | "inflight": "^1.0.4", 85 | "inherits": "2", 86 | "minimatch": "^3.1.1", 87 | "once": "^1.3.0", 88 | "path-is-absolute": "^1.0.0" 89 | }, 90 | "engines": { 91 | "node": "*" 92 | }, 93 | "funding": { 94 | "url": "https://github.com/sponsors/isaacs" 95 | } 96 | }, 97 | "node_modules/glob/node_modules/brace-expansion": { 98 | "version": "1.1.11", 99 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 100 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 101 | "dev": true, 102 | "dependencies": { 103 | "balanced-match": "^1.0.0", 104 | "concat-map": "0.0.1" 105 | } 106 | }, 107 | "node_modules/glob/node_modules/minimatch": { 108 | "version": "3.1.2", 109 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 110 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 111 | "dev": true, 112 | "dependencies": { 113 | "brace-expansion": "^1.1.7" 114 | }, 115 | "engines": { 116 | "node": "*" 117 | } 118 | }, 119 | "node_modules/graceful-fs": { 120 | "version": "4.2.10", 121 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 122 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", 123 | "dev": true 124 | }, 125 | "node_modules/inflight": { 126 | "version": "1.0.6", 127 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 128 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 129 | "dev": true, 130 | "dependencies": { 131 | "once": "^1.3.0", 132 | "wrappy": "1" 133 | } 134 | }, 135 | "node_modules/inherits": { 136 | "version": "2.0.4", 137 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 138 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 139 | "dev": true 140 | }, 141 | "node_modules/minimatch": { 142 | "version": "5.0.1", 143 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", 144 | "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", 145 | "dev": true, 146 | "dependencies": { 147 | "brace-expansion": "^2.0.1" 148 | }, 149 | "engines": { 150 | "node": ">=10" 151 | } 152 | }, 153 | "node_modules/ms": { 154 | "version": "2.1.2", 155 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 156 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 157 | "dev": true 158 | }, 159 | "node_modules/once": { 160 | "version": "1.4.0", 161 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 162 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 163 | "dev": true, 164 | "dependencies": { 165 | "wrappy": "1" 166 | } 167 | }, 168 | "node_modules/path-is-absolute": { 169 | "version": "1.0.1", 170 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 171 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 172 | "dev": true, 173 | "engines": { 174 | "node": ">=0.10.0" 175 | } 176 | }, 177 | "node_modules/wrappy": { 178 | "version": "1.0.2", 179 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 180 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 181 | "dev": true 182 | } 183 | }, 184 | "dependencies": { 185 | "balanced-match": { 186 | "version": "1.0.2", 187 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 188 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 189 | "dev": true 190 | }, 191 | "brace-expansion": { 192 | "version": "2.0.1", 193 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 194 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 195 | "dev": true, 196 | "requires": { 197 | "balanced-match": "^1.0.0" 198 | } 199 | }, 200 | "concat-map": { 201 | "version": "0.0.1", 202 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 203 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 204 | "dev": true 205 | }, 206 | "debug": { 207 | "version": "4.3.4", 208 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 209 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 210 | "dev": true, 211 | "requires": { 212 | "ms": "2.1.2" 213 | } 214 | }, 215 | "folder-hash": { 216 | "version": "4.0.2", 217 | "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-4.0.2.tgz", 218 | "integrity": "sha512-Iw9GCqdA+zHfDVvk90TSAV66jq0IwiZaPvPgUiW+DHRwnaPOeZomzlgutx9QclinsQGz/XcVIGlDEJbFhCV5wA==", 219 | "dev": true, 220 | "requires": { 221 | "debug": "^4.3.3", 222 | "graceful-fs": "~4.2.9", 223 | "minimatch": "~5.0.0" 224 | } 225 | }, 226 | "fs.realpath": { 227 | "version": "1.0.0", 228 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 229 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 230 | "dev": true 231 | }, 232 | "glob": { 233 | "version": "7.2.3", 234 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 235 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 236 | "dev": true, 237 | "requires": { 238 | "fs.realpath": "^1.0.0", 239 | "inflight": "^1.0.4", 240 | "inherits": "2", 241 | "minimatch": "^3.1.1", 242 | "once": "^1.3.0", 243 | "path-is-absolute": "^1.0.0" 244 | }, 245 | "dependencies": { 246 | "brace-expansion": { 247 | "version": "1.1.11", 248 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 249 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 250 | "dev": true, 251 | "requires": { 252 | "balanced-match": "^1.0.0", 253 | "concat-map": "0.0.1" 254 | } 255 | }, 256 | "minimatch": { 257 | "version": "3.1.2", 258 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 259 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 260 | "dev": true, 261 | "requires": { 262 | "brace-expansion": "^1.1.7" 263 | } 264 | } 265 | } 266 | }, 267 | "graceful-fs": { 268 | "version": "4.2.10", 269 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 270 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", 271 | "dev": true 272 | }, 273 | "inflight": { 274 | "version": "1.0.6", 275 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 276 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 277 | "dev": true, 278 | "requires": { 279 | "once": "^1.3.0", 280 | "wrappy": "1" 281 | } 282 | }, 283 | "inherits": { 284 | "version": "2.0.4", 285 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 286 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 287 | "dev": true 288 | }, 289 | "minimatch": { 290 | "version": "5.0.1", 291 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", 292 | "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", 293 | "dev": true, 294 | "requires": { 295 | "brace-expansion": "^2.0.1" 296 | } 297 | }, 298 | "ms": { 299 | "version": "2.1.2", 300 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 301 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 302 | "dev": true 303 | }, 304 | "once": { 305 | "version": "1.4.0", 306 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 307 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 308 | "dev": true, 309 | "requires": { 310 | "wrappy": "1" 311 | } 312 | }, 313 | "path-is-absolute": { 314 | "version": "1.0.1", 315 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 316 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 317 | "dev": true 318 | }, 319 | "wrappy": { 320 | "version": "1.0.2", 321 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 322 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 323 | "dev": true 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 |
5 | Kasm Workspaces Registry 6 |
7 |

8 | 9 |

This repository is a template you can use to create your own registry that will work with Kasm Workspaces. A front end website is automatically generated for you and will look similar to the one below.

10 | 11 | ![image](https://user-images.githubusercontent.com/5698566/230064289-9e8967a1-4ff9-43f3-8495-f4170c23a80f.png) 12 | 13 | ## Contents 14 | 15 | 1. [Create your own repository](#1-create-your-own-repository) 16 | - [Use this template](#use-this-template) 17 | - [Configure repository](#configure-repository) 18 | 1. [Check workflows are running](#2-check-workflows-are-running) 19 | 1. [Edit the config variables](#3-edit-the-config-variables) 20 | - [Modify next.config.js](#modify-nextconfigjs) 21 | - [Settings definitions](#settings-definitions) 22 | - [Commit changes](#commit-changes) 23 | 1. [Set up GitHub pages](#4-set-up-github-pages) 24 | - [Initial setup](#initial-setup) 25 | - [Visit the site](#visit-the-site) 26 | - [Check build progress](#check-build-progress) 27 | - [Checking it works](#checking-it-works) 28 | 1. [Creating Workspaces](#5-creating-workspaces) 29 | - [Folder structure](#folder-structure) 30 | - [Schema](#schema) 31 | - [New schema version](#new-schema-version) 32 | 1. [Discovery](#6-dicovery) 33 | 34 | 35 |   36 | 37 | ## 1. Create your own repository 38 | 39 | ### Use this template 40 | 41 | 42 | 43 | 1. Click on the green **Use this template** button. 44 | 2. Select **Create a new repository** from the dropdown. 45 | 46 | 47 | ### Configure repository 48 | 49 | ![use-template](https://user-images.githubusercontent.com/5698566/230080115-e81a5663-5752-44fc-94a5-ea7721e5745b.gif) 50 | 51 | > **Note** 52 | > If you set the **Repository name** to `kasm-registry` you wont have to make any changes to the `baseUrl` later, unless you want to use a (sub)domain. 53 | 54 | 1. Select a **Repository name**, this name will also be used later for the `baseUrl`, 55 | 2. Make sure it's set as a **Public** repository 56 | 3. Tick the **Include all branches** checkbox, 57 | 4. Click on the **Create repository from template** button. 58 | 59 |   60 | 61 | ## 2. Check workflows are running 62 | 63 | 64 | 65 | > **Note** 66 | > If you see something in the table that looks similar to the above then it's working. If it needs enabling this section will probably be blank with a message about workflows needing to be enabled. 67 | 68 | Click on the **Actions** tab in the top menu and check whether workflows need enabling, if they do, enable them, otherwise you should be good to go. 69 | 70 |   71 | 72 | ## 3. Edit the config variables 73 | 74 | ### Modify next.config.js 75 | ![edit-conf](https://user-images.githubusercontent.com/5698566/230075839-ec698589-1276-4163-bd5e-34642a108e7f.gif) 76 | 77 | 1. Go back to the `Code` tab 78 | 1. Click the `site` folder 79 | 2. Click on the `next.config.js` file 80 | 1. Click the edit button. 81 | 2. Fill in the `env` section with the relevant information and change the basePath if needed (details below). 82 | 83 | ### Settings definitions 84 | 85 | 86 | 87 | | Property | Description | 88 | | --------- | ----------- | 89 | | env.name | The name you want to display for your registry. | 90 | | env.description | A short description to display when a store's information button is pressed. | 91 | | env.icon | The image to display for your registry. You can upload an image to `/site/public/` and reference that by https://domain.com/1.0/image.png or if you aren't using a {sub}domain by referencing it from https://username.github.io/repositoryname/1.0/image.png where image.png is the name of the image you uploaded. Alternatively just put the url of an image available on the web. If you just want to get the registry up and working, leave the default value in place until later. | 92 | | env.listUrl | The link to the root of your site. For example https://username.github.io/repositoryname/ it should always include a trailing slash. | 93 | | env.contactUrl | A link users can use to contact you on, such as your github issues page (right click the **Issues** tab in the top menu - next to the **Code** tab - and select `copy link address` and paste that in). | 94 | | basePath | If you are using a domain or a subdomain, your basePath will just be `basePath: '/1.0',`, otherwise change the value to include what you chose for the repository name in step 2 `basePath: '/repositoryname/1.0',`. **The `1.0` will be replaced with the branch name automatically, so you should always keep it as 1.0.** | 95 | 96 | ### Commit changes 97 | image 98 | 99 | 100 | 1. Scroll down to the bottom of the page 101 | 2. Enter a commit message (You can also leave this blank) 102 | 3. Click the **Commit changes** button. 103 | 104 |   105 | 106 | ## 4. Set up GitHub pages 107 | 108 | ### Initial setup 109 | 110 | ![gh-pages](https://user-images.githubusercontent.com/5698566/230077012-938704c4-af44-4d3c-8023-0732c34a78bc.gif) 111 | 112 | > **Note** 113 | > If you ticked the "Include all branches" checkbox in step 1, this should all be set up for you, if not, just follow the instructions below 114 | 115 | 1. Go to the **Settings** top menu tab 116 | 2. Click the **Pages** left menu item 117 | 3. In the **Build and deployment** section, under the **Branch** heading, make sure the dropdown is set to gh-pages, if not, set it and click **Save**. 118 | 119 | ### Visit the site 120 | 121 | If you see a message like the following: 122 | 123 | ![image](https://user-images.githubusercontent.com/5698566/230061310-f2883e2f-7279-45c9-8cc5-de56dcc6e03f.png) 124 | 125 | Then congratulations, you should have a working site! Just click the **Visit Site** button. **However**, you changes may not have finished building yet, so before clicking the button it's advised to check the build process first. 126 | 127 | ### Check build progress 128 | 129 | If you don't see that button yet, then not to worry, it's likely that you are just too quick (also if you do see the button but it doesn't reflect the changes you made, this next bit is relevant as well) 130 | 131 | Check on the CI progress in the **Actions** tab, 132 | 133 | image 134 | 135 | Once `Page build and deployment` is finished go back to Settings / Pages and you should have a live site. Click on the Visit Site button. 136 | 137 | **You should now have a working site which includes any workspaces you added or the default if you haven't made any changes yet** 138 | 139 | image 140 | 141 | ### Checking it works 142 | 143 | ![install-registry-800](https://user-images.githubusercontent.com/5698566/230379178-4b2a08c7-3ae1-4000-88a0-ae4b8ab17892.gif) 144 | 145 | > **Note** 146 | > If you copy the url from the address bar instead of clicking the button, be sure to remove the branch version from the URL when adding to workspaces, otherwise it wont work. 147 | 148 | 1. Click on the **Workspace Registry Link** button, this will put the correct url in your clipboard. 149 | 2. Go to your Kasm Workspaces instance. 150 | 3. Navigate to the Workspaces Registry (Admin / Workspaces / Click on the **Workspaces Registry** button). 151 | 4. Click on **Add new** in the registries list. 152 | 5. Paste the URL into the text box and click **Add Registry** 153 | 6. Click on the mini icon under the registry name to filter by your registries workspaces 154 | 155 |   156 | 157 | ## 5. Creating workspaces 158 | 159 | Once you are ready to upload your workspaces, head back to the **Code** tab. You can either continue using the online editor or you might find it easier to clone the repository and work on a local copy, it's up to you. For this example we will continue with the online editor. 160 | 161 | ### Folder structure 162 | 163 | ![workspaces-800](https://user-images.githubusercontent.com/5698566/230384525-d8577582-fab7-4850-979d-d75e83503022.gif) 164 | 165 | All workspaces reside in the **workspaces** folder 166 | 167 | You will need to create a folder and the necessary files using the following format: 168 | 169 | ``` 170 | Workspace Name 171 | - workspace.json 172 | - workspace-name.png 173 | ``` 174 | 175 | ![add-workspace-800](https://user-images.githubusercontent.com/5698566/230386427-c2221647-ce30-4c2e-bc92-e83481d1b8ba.gif) 176 | 177 | **Folder name** - The folder name can be whatever it needs to be. You probably want to stay clear of special characters to be on the safe side, but spaces should be fine. 178 | 179 | **workspace.json** - This is a JSON file with all the parameters you want to be sent to Kasm Workspaces when it builds the container. You can see the valid paramaters in the schema section and whether they are required or not. 180 | 181 | ``` 182 | { 183 | "description": "Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.", 184 | "docker_registry": "https://index.docker.io/v1/", 185 | "name": "kasmweb/vs-code:develop", 186 | "image_src": "vs-code.png", 187 | "categories": [ 188 | "Development" 189 | ], 190 | "friendly_name": "Visual Studio Code", 191 | "architecture": [ 192 | "amd64", 193 | "arm64" 194 | ], 195 | "compatibility": [ 196 | "1.13.x" 197 | ], 198 | "uncompressed_size_mb": 2170 199 | } 200 | ``` 201 | 202 | **Image file** - The image can be `.png` or `.svg` and ideally will be square and at least 50 x 50px. If you use the workspace builder on your registry store front it will try to normalise everything to make it simpler. 203 | 204 | Don't forget to commit your changes! 205 | 206 | ### Schema 207 | 208 | **Version** 1.0 209 | 210 | | Property | Required | Type | Description | 211 | | --------------------- | -------- | --- | --- | 212 | | friendly_name | True | String | The name to show | 213 | | name | True | String | The docker image to use | 214 | | description | True | String | A short description of the workspace | 215 | | image_src | True | String | The name of the workspace icon used | 216 | | architecture | True | Array | Json list containing either "amd64", "arm64" or both | 217 | | compatability | True | Array | A list of Kasm versions the workspace should work with | 218 | | uncompressed_size_mb | True | Integer | Integer of the approximate size of the workspace when it's uncompressed in MB. This doesn't take into account layers. For example if an image is 2.46GB you would enter 2460 | 219 | | categories | False | Array | Json list containing the categories the workspace belongs too. This should be limited to a max of 3. | 220 | | docker_registry | False | String | Which docker registry to use | 221 | | run_config | False | Object | Any additional parameters to add to the run config | 222 | | exec_config | False | Object | Any additional parameters to add to the exec config | 223 | | notes | False | String | Notes about running the workspace, such as if it requires libseccomp. | 224 | | cores | False | Integer | Specify the amount of cores to use for this workspace | 225 | | memory | False | Integer | Specify the amount of memory to use for this workspace | 226 | | gpu_count | False | Integer | Specify the amount of GPUs to use for this workspace | 227 | | cpu_allocation_method | False | String | What CPU allocation method to use for this workspace. Can be either "Inherit", "Quotas" or "Shares" | 228 | 229 | Head to the **Actions** tab to check your progress and once `Page build and deployment` is complete, your site should be ready. 230 | 231 | 232 | ### New schema version 233 | 234 | When a new schema version comes out, you just need to create a new branch that refrlects the new schema, for example `1.1` and make it the default branch. 235 | 236 | In the new branch, make any updates that are needed, when the changes are committed a new version will be built. 237 | 238 | Kasm Workspaces will automatically pull the version of the schema that it understands. 239 | 240 |   241 | 242 | ## 6. Discovery 243 | 244 | The tag below will hopefully make it easier for people to find your Workspace Registry by clicking on [this github search link](https://github.com/search?q=in%3Areadme+sort%3Aupdated+-user%3Akasmtech+%22KASM-REGISTRY-DISCOVERY-IDENTIFIER%22&type=repositories). If you want to make it harder to find your repository for some reason, just remove this section. 245 | 246 | If you are the one doing the searching, click on the **site** folder, then click on **next.config.js** and the url can be found under **env.listUrl** 247 | 248 | ![search-600](https://user-images.githubusercontent.com/5698566/230614274-2976b4d7-074f-4e6d-9e58-e4d2512a3d2a.gif) 249 | 250 | KASM-REGISTRY-DISCOVERY-IDENTIFIER 251 | -------------------------------------------------------------------------------- /site/pages/new/[[...workspace]].js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { useState, useEffect, useRef } from 'react' 3 | import { saveAs } from 'file-saver'; 4 | import CreatableSelect from 'react-select/creatable'; 5 | import Select from 'react-select'; 6 | import { useRouter } from 'next/router' 7 | import allworkspaces from '../../../public/list.json' 8 | 9 | 10 | export async function getStaticPaths() { 11 | let paths = allworkspaces.workspaces.map(workspace => ({ 12 | params: { 13 | workspace: [btoa(workspace.friendly_name)] 14 | } 15 | })) 16 | paths.push({ 17 | params: { workspace: null } 18 | }) 19 | return { 20 | paths, 21 | fallback: false, // can also be true or 'blocking' 22 | } 23 | } 24 | 25 | // `getStaticPaths` requires using `getStaticProps` 26 | export async function getStaticProps({ params }) { 27 | const workspace = params.workspace 28 | return { 29 | // Passed to the page component as props 30 | props: { workspace: workspace ?? null }, 31 | } 32 | } 33 | 34 | export default function New({ workspace }) { 35 | 36 | const name = useRef(null); 37 | const friendly_name = useRef(null); 38 | const description = useRef(null); 39 | 40 | const [categories, setCategories] = useState(null) 41 | const [architecture, setArchitecture] = useState(null) 42 | const [icon, setIcon] = useState(null) 43 | const [ext, setExt] = useState('png') 44 | const [inlineImage, setInlineImage] = useState(null) 45 | 46 | const defaultState = { 47 | friendly_name: null, 48 | image_src: null, 49 | description: null, 50 | name: null, 51 | cores: 2, 52 | memory: 2768, 53 | gpu_count: 0, 54 | cpu_allocation_method: "Inherit", 55 | docker_registry: "https://index.docker.io/v1/", 56 | categories: [], 57 | require_gpu: false, 58 | enabled: true, 59 | image_type: 'Container', 60 | } 61 | 62 | const [combined, setCombined] = useState(defaultState) 63 | 64 | const router = useRouter() 65 | // const { workspace } = router.query 66 | 67 | useEffect(() => { 68 | if(workspace === null) { 69 | description.current.value = '' 70 | name.current.value = '' 71 | friendly_name.current.value = '' 72 | setCategories(null) 73 | setArchitecture(null) 74 | setIcon(null) 75 | setCombined(defaultState) 76 | } 77 | else if (workspace && workspace[0]) { 78 | const workspaceDetails = allworkspaces.workspaces.find(el => el.friendly_name === atob(workspace[0])) 79 | delete workspaceDetails['sha'] 80 | description.current.value = workspaceDetails.description 81 | name.current.value = workspaceDetails.name 82 | friendly_name.current.value = workspaceDetails.friendly_name 83 | if (workspaceDetails.categories) { 84 | let catMap = [] 85 | workspaceDetails.categories.map((e) => catMap.push({ 86 | label: e, 87 | value: e, 88 | })) 89 | setCategories(catMap) 90 | } 91 | if (workspaceDetails.architecture) { 92 | let archMap = [] 93 | workspaceDetails.architecture.map((e) => archMap.push({ 94 | label: e, 95 | value: e, 96 | })) 97 | setArchitecture(archMap) 98 | } 99 | 100 | setInlineImage('../../icons/' + workspaceDetails.image_src) 101 | 102 | setCombined({ 103 | ...combined, 104 | ...workspaceDetails 105 | }) 106 | } 107 | }, [workspace]) 108 | 109 | const displayWorkspace = () => { 110 | return { 111 | ...combined, 112 | // categories: JSON.stringify(combined.categories) 113 | } 114 | } 115 | 116 | const customStyles = { 117 | control: (base, state) => ({ 118 | ...base, 119 | background: "#f1f5f9", 120 | borderRadius: '0.5rem', 121 | borderColor: "#94a3b8" 122 | }), 123 | multiValue: (styles, { data }) => { 124 | return { 125 | ...styles, 126 | backgroundColor: '#dde6f1', 127 | }; 128 | } 129 | } 130 | 131 | useEffect(() => { 132 | if (combined && combined.friendly_name) { 133 | const updateWorkspace = { 134 | ...combined 135 | } 136 | updateWorkspace.image_src = friendlyUrl(updateWorkspace.friendly_name) + '.' + ext 137 | setCombined(updateWorkspace) 138 | } 139 | }, [ext]) 140 | 141 | const updateCategories = (items) => { 142 | const updateWorkspace = { 143 | ...combined 144 | } 145 | updateWorkspace.categories = items.map(cat => cat.value) 146 | setCombined(updateWorkspace) 147 | let catMap = [] 148 | updateWorkspace.categories.map((e) => catMap.push({ 149 | label: e, 150 | value: e, 151 | })) 152 | setCategories(catMap) 153 | } 154 | 155 | const updateArchitecture = (items) => { 156 | const updateWorkspace = { 157 | ...combined 158 | } 159 | updateWorkspace.architecture = items.map(arch => arch.value) 160 | setCombined(updateWorkspace) 161 | let archMap = [] 162 | updateWorkspace.architecture.map((e) => archMap.push({ 163 | label: e, 164 | value: e, 165 | })) 166 | setArchitecture(archMap) 167 | } 168 | 169 | function friendlyUrl(url) { 170 | // make the url lowercase 171 | var encodedUrl = url.toString().toLowerCase(); 172 | // replace & with and 173 | encodedUrl = encodedUrl.split(/\&+/).join("-and-") 174 | // remove invalid characters 175 | encodedUrl = encodedUrl.split(/[^a-z0-9]/).join("-"); 176 | // remove duplicates 177 | encodedUrl = encodedUrl.split(/-+/).join("-"); 178 | // trim leading & trailing characters 179 | encodedUrl = encodedUrl.trim('-'); 180 | return encodedUrl; 181 | } 182 | 183 | const downloadZip = () => { 184 | var JSZip = require("jszip"); 185 | const zip = new JSZip() 186 | const folder = zip.folder(combined.friendly_name) 187 | folder.file('workspace.json', JSON.stringify(combined, null, 2)) 188 | if (icon) { 189 | folder.file(combined.image_src, icon.file) 190 | } 191 | else if (inlineImage) { 192 | const promise = fetch(inlineImage).then(response => response.blob()) 193 | folder.file(combined.image_src, promise) 194 | } 195 | zip.generateAsync({ type: "blob" }) 196 | .then(function (content) { 197 | // Force down of the Zip file 198 | saveAs(content, friendlyUrl(combined.friendly_name) + '.zip'); 199 | }); 200 | } 201 | 202 | const handleChange = (event) => { 203 | const updateWorkspace = { 204 | ...combined 205 | } 206 | updateWorkspace[event.target.name] = event.target.value 207 | if (event.target.name === 'icon') { 208 | delete updateWorkspace.icon 209 | setIcon({ 210 | value: event.target.value, 211 | file: event.target.files[0] 212 | }) 213 | setExt(event.target.value.substr(event.target.value.lastIndexOf('.') + 1)) 214 | setInlineImage(null) 215 | // return 216 | } 217 | 218 | if (updateWorkspace.friendly_name) { 219 | updateWorkspace.image_src = friendlyUrl(updateWorkspace.friendly_name) + '.' + ext 220 | } 221 | 222 | setCombined(updateWorkspace) 223 | } 224 | 225 | const options = [ 226 | { value: 'Browser', label: 'Browser' }, 227 | { value: 'Communication', label: 'Communication' }, 228 | { value: 'Desktop', label: 'Desktop' }, 229 | { value: 'Development', label: 'Development' }, 230 | { value: 'Games', label: 'Games' }, 231 | { value: 'Multimedia', label: 'Multimedia' }, 232 | { value: 'Office', label: 'Office' }, 233 | { value: 'Privacy', label: 'Privacy' }, 234 | { value: 'Productivity', label: 'Productivity' }, 235 | { value: 'Remote Access', label: 'Remote Access' } 236 | ] 237 | 238 | return ( 239 |
240 | 241 | Kasm Workspaces 242 | 243 | 244 | 245 |
246 |
247 |

Add Workspace

248 |
249 |

This page is designed to allow admins to generate the JSON they need to upload to the "workspaces" directory. It also allows end users to see what settings are needed if they want to manually copy them into a new workspace.

250 | 251 | 252 | 253 |

Select the image to use, image will be renamed when it's downloaded.

254 | 255 | 256 | 257 |

This is the name that will show for users

258 | 259 | 260 | 269 |

You can select from the available option or create new ones.

270 | 271 | 272 | 273 |

A short description about the workspace

274 | 275 | 276 | 277 |

The docker image to use, i.e. kasmweb/filezilla:develop

278 | 279 | 280 |