From 8a4c513331c3764f232cfc70a5a6e3f8b1d1d1b6 Mon Sep 17 00:00:00 2001 From: Chris Hunt Date: Thu, 19 Jan 2023 11:18:02 +0000 Subject: [PATCH] Change Applications to Workspaces --- README.md | 14 +- apps/README.md | 7 - processing/processjson.js | 14 +- site/.env.local | 1 - site/components/App.js | 30 --- site/components/Workspace.js | 30 +++ site/components/header.js | 38 ++-- site/next.config.js | 4 +- site/pages/index.js | 26 +-- .../[[...app]].js => new/[[...workspace]].js} | 205 +++++++++--------- workspaces/README.md | 7 + 11 files changed, 186 insertions(+), 190 deletions(-) delete mode 100644 apps/README.md delete mode 100644 site/.env.local delete mode 100644 site/components/App.js create mode 100644 site/components/Workspace.js rename site/pages/{addapp/[[...app]].js => new/[[...workspace]].js} (72%) create mode 100644 workspaces/README.md diff --git a/README.md b/README.md index cae39aa..1406291 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # THIS IS CURRENTLY IN DEVELOPMENT AND NOT READY FOR PRIME TIME YET, WE'LL ANNOUNCE WHEN IT'S INCLUDED IN BUILDS. FEEL FREE TO PLAY WITH IT THOUGH AND GIVE FEEDBACK. -# Kasm Apps +# Kasm Workspaces -This is a repository of the apps supported by Kasm. The apps list is automatically generated and can be used when creating new workspaces or using the 1 click installer +This is a repository of the workspaces supported by Kasm. The workspaces list is automatically generated and can be used when creating new workspaces or using the 1 click installer -## Create your own app store +## Create your own workspace registry -We have tried to make it as simple as possible for people to create their own app stores that work with Kasm, the easiest way to do that is to follow these steps: +We have tried to make it as simple as possible for people to create their own registries that work with Kasm, the easiest way to do that is to follow these steps: 1. Click on "Use this template", select Create a new repository 1. Select a Repository name, you will need to use this name later in the process as well, tick the "Include all branches" checkbox, then click on the "Create repository from template" button @@ -18,11 +18,11 @@ We have tried to make it as simple as possible for people to create their own ap * listUrl - The link to the root of your site. For example https://username.github.io/repositoryname/ it should always include a trailing slash. * contactUrl - A link users can use to contact you on. If you are using a domain or a subdomain, your basePath will just be the current version number `basePath: '/1.0',`, otherwise change the value to include what you chose for the repository name in step 2 `basePath: '/repositoryname/1.0',`. -1. Upload your apps to the /apps folder +1. Upload your workspaces to the /workspaces folder 1. Go to Settings then Pages and select Branch - gh-pages and click Save 1. Check progress in Actions 1. Once complete go back to Settings / Pages and you should have a live site. Click on the Visit Site button. -1. You should now have a working site which includes any apps you added +1. You should now have a working site which includes any workspaces you added [![](https://cdn.loom.com/sessions/thumbnails/256fac3d2bbb422b8e779ac1c8244d33-00001.gif)](https://www.loom.com/share/256fac3d2bbb422b8e779ac1c8244d33 "") @@ -35,6 +35,6 @@ If a new schema version comes out, this is what you will need to do. ## Discovery -The tag below will hopefully make it easier for people to find your App 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. +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. KASM-REGISTRY-DISCOVERY-IDENTIFIER \ No newline at end of file diff --git a/apps/README.md b/apps/README.md deleted file mode 100644 index 844aa10..0000000 --- a/apps/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Apps directory - -This directory is for storing all the files needed for your apps store, they should be stored with the following structure: - -* apps/Application Name -* apps/Application Name/app.json -* apps/Application Name/application-name.png \ No newline at end of file diff --git a/processing/processjson.js b/processing/processjson.js index 82d93e8..14584fe 100644 --- a/processing/processjson.js +++ b/processing/processjson.js @@ -12,7 +12,7 @@ if (!fs.existsSync(dir + "/icons")) { fs.mkdirSync(dir + "/icons"); } -glob("**/app.json", async function (err, files) { +glob("**/workspace.json", async function (err, files) { if (err) { console.log( "cannot read the folder, something goes wrong with glob", @@ -20,8 +20,8 @@ glob("**/app.json", async function (err, files) { ); } - let apptotal = files.length; - let apps = []; + let workspacetotal = files.length; + let workspaces = []; let promises = []; const options = { @@ -32,7 +32,7 @@ glob("**/app.json", async function (err, files) { for (const file of files) { //files.forEach(async function(file) { - let folder = file.replace("/app.json", ""); + let folder = file.replace("/workspace.json", ""); let hash = await hashElement(folder, options); let filedata = fs.readFileSync(file); @@ -40,7 +40,7 @@ glob("**/app.json", async function (err, files) { let parsed = JSON.parse(filedata); parsed.sha = hash.hash; console.log(parsed.name + ' added') - apps.push(parsed); + workspaces.push(parsed); if (fs.existsSync(folder + "/" + parsed.image_src)) { let imagedata = fs.readFileSync(folder + "/" + parsed.image_src); @@ -53,13 +53,13 @@ glob("**/app.json", async function (err, files) { let json = { name: nextConfig.env.name || 'Unknown store', - appcount: apptotal, + workspacecount: workspacetotal, icon: nextConfig.env.icon || null, description: nextConfig.env.description || null, list_url: nextConfig.env.listUrl || null, contact_url: nextConfig.env.contactUrl || null, modified: Date.now(), - apps: apps, + workspaces: workspaces, }; let data = JSON.stringify(json); diff --git a/site/.env.local b/site/.env.local deleted file mode 100644 index 914b54b..0000000 --- a/site/.env.local +++ /dev/null @@ -1 +0,0 @@ -NEXT_PUBLIC_APPURL=apps.kasmweb.com/list.json \ No newline at end of file diff --git a/site/components/App.js b/site/components/App.js deleted file mode 100644 index da95442..0000000 --- a/site/components/App.js +++ /dev/null @@ -1,30 +0,0 @@ -import { useRouter } from 'next/router' - -function App({ Component, pageProps, app }) { - const router = useRouter() - - const viewexample = (app) => { - router.push({ - pathname: '/addapp/[app]', - query: { app: btoa(app.name)} - }) - } - - return ( -
viewexample(app)} className="w-[245px] h-[88px] transition-all relative cursor-pointer group flex p-2 items-center justify-center bg-slate-100/90 dark:bg-slate-900/90 shadow rounded hover:shadow-xl hover:bg-gradient-to-r hover:from-slate-900 hover:to-cyan-800 hover:text-white"> -
-
-
- -
-
-
{ app.friendly_name }
-

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

-
-
-
-
- ) -} - -export default App \ No newline at end of file diff --git a/site/components/Workspace.js b/site/components/Workspace.js new file mode 100644 index 0000000..1359c3c --- /dev/null +++ b/site/components/Workspace.js @@ -0,0 +1,30 @@ +import { useRouter } from 'next/router' + +function Workspace({ Component, pageProps, workspace }) { + const router = useRouter() + + const viewexample = (workspace) => { + router.push({ + pathname: '/new/[workspace]', + query: { workspace: btoa(workspace.name)} + }) + } + + return ( +
viewexample(workspace)} className="w-[245px] h-[88px] transition-all relative cursor-pointer group flex p-2 items-center justify-center bg-slate-100/90 dark:bg-slate-900/90 shadow rounded hover:shadow-xl hover:bg-gradient-to-r hover:from-slate-900 hover:to-cyan-800 hover:text-white"> +
+
+
+ +
+
+
{ workspace.friendly_name }
+

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

+
+
+
+
+ ) +} + +export default Workspace \ No newline at end of file diff --git a/site/components/header.js b/site/components/header.js index c0b19e5..18db522 100644 --- a/site/components/header.js +++ b/site/components/header.js @@ -23,38 +23,36 @@ export default function Header({ searchText, changeSearch }) {
{process.env.name}
- A - p - p - l - i - c - a - t - i + W o - n + r + k + s + p + a + c + e   - D - a - t - a - b - a - s + R e + g + i + s + t + r + y
diff --git a/site/next.config.js b/site/next.config.js index 549e5be..b1fd0a2 100644 --- a/site/next.config.js +++ b/site/next.config.js @@ -3,14 +3,14 @@ const nextConfig = { env: { name: 'Kasm Technologies', - description: 'The official store for Kasm supported applications.', + description: 'The official store for Kasm supported workspaces.', icon: '/img/logo.svg', listUrl: 'https://registry.kasmweb.com/', contactUrl: 'https://kasmweb.com/support', }, reactStrictMode: true, swcMinify: true, - basePath: '/kasm-apps/1.0', + basePath: '/kasm-registry/1.0', trailingSlash: true, images: { unoptimized: true, diff --git a/site/pages/index.js b/site/pages/index.js index cd2b64c..d3233f2 100644 --- a/site/pages/index.js +++ b/site/pages/index.js @@ -1,24 +1,24 @@ import { useState, useEffect } from 'react' import Head from 'next/head' -import App from '../components/App' +import Workspace from '../components/Workspace' import styles from '../styles/Home.module.css' export default function Home({ searchText }) { - const [apps, setApps] = useState(null) + const [workspaces, setWorkspaces] = useState(null) useEffect(() => { fetch('list.json') .then((res) => res.json()) - .then((apps) => { - setApps(apps) + .then((workspaces) => { + setWorkspaces(workspaces) }) }, []) - let filteredapps = apps && apps.apps && apps.apps.length > 0 ? [...apps.apps] : []; + let filteredworkspaces = workspaces && workspaces.workspaces && workspaces.workspaces.length > 0 ? [...workspaces.workspaces] : []; const lowerSearch = searchText && searchText.toLowerCase(); if (searchText && searchText !== "") { - filteredapps = filteredapps.filter((i) => { + filteredworkspaces = filteredworkspaces.filter((i) => { const category = (i.categories && i.categories.length > 0) ? i.categories.filter((i) => i.toLowerCase().includes(lowerSearch) ) : []; @@ -33,20 +33,20 @@ export default function Home({ searchText }) { return (
- Kasm Apps - + Kasm Workspaces +
-

Applications: {apps && apps.appcount}

+

Workspaces: {workspaces && workspaces.workspacecount}

- {filteredapps && filteredapps.length > 0 && filteredapps.map(function (app, i) { - return + {filteredworkspaces && filteredworkspaces.length > 0 && filteredworkspaces.map(function (workspace, i) { + return })} - {filteredapps && filteredapps.length === 0 && ( -

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

+ {filteredworkspaces && filteredworkspaces.length === 0 && ( +

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

)}
diff --git a/site/pages/addapp/[[...app]].js b/site/pages/new/[[...workspace]].js similarity index 72% rename from site/pages/addapp/[[...app]].js rename to site/pages/new/[[...workspace]].js index 9a807f3..3a562fb 100644 --- a/site/pages/addapp/[[...app]].js +++ b/site/pages/new/[[...workspace]].js @@ -4,17 +4,17 @@ import { saveAs } from 'file-saver'; import CreatableSelect from 'react-select/creatable'; import Select from 'react-select'; import { useRouter } from 'next/router' -import allapps from '../../../public/list.json' +import allworkspaces from '../../../public/list.json' export async function getStaticPaths() { - let paths = allapps.apps.map(app => ({ + let paths = allworkspaces.workspaces.map(workspace => ({ params: { - app: [btoa(app.name)] + workspace: [btoa(workspace.name)] } })) paths.push({ - params: { app: null } + params: { workspace: null } }) return { paths, @@ -24,47 +24,14 @@ export async function getStaticPaths() { // `getStaticPaths` requires using `getStaticProps` export async function getStaticProps({ params }) { - const app = params.app + const workspace = params.workspace return { // Passed to the page component as props - props: { app: app ?? null }, + props: { workspace: workspace ?? null }, } } -export default function AddApp({ app }) { - - function friendlyUrl(url) { - // make the url lowercase - var encodedUrl = url.toString().toLowerCase(); - // replace & with and - encodedUrl = encodedUrl.split(/\&+/).join("-and-") - // remove invalid characters - encodedUrl = encodedUrl.split(/[^a-z0-9]/).join("-"); - // remove duplicates - encodedUrl = encodedUrl.split(/-+/).join("-"); - // trim leading & trailing characters - encodedUrl = encodedUrl.trim('-'); - return encodedUrl; - } - - const downloadZip = () => { - var JSZip = require("jszip"); - const zip = new JSZip() - const folder = zip.folder(application.friendly_name) - folder.file('app.json', JSON.stringify(application, null, 2)) - if (icon) { - folder.file(application.image_src, icon.file) - } - else if (inlineImage) { - const promise = fetch(inlineImage).then(response => response.blob()) - folder.file(application.image_src, promise) - } - zip.generateAsync({ type: "blob" }) - .then(function (content) { - // Force down of the Zip file - saveAs(content, friendlyUrl(application.friendly_name) + '.zip'); - }); - } +export default function New({ workspace }) { const name = useRef(null); const friendly_name = useRef(null); @@ -92,58 +59,58 @@ export default function AddApp({ app }) { image_type: 'Container', } - const [application, setApplication] = useState(defaultState) + const [workspace, setWorkspace] = useState(defaultState) const router = useRouter() - // const { app } = router.query + // const { workspace } = router.query useEffect(() => { - console.log(app) - if(app === null) { + console.log(workspace) + if(workspace === null) { description.current.value = '' name.current.value = '' friendly_name.current.value = '' setCategories(null) setArchitecture(null) setIcon(null) - setApplication(defaultState) + setWorkspace(defaultState) } - else if (app && app[0]) { - const appDetails = allapps.apps.find(el => el.name === atob(app[0])) - delete appDetails['sha'] - description.current.value = appDetails.description - name.current.value = appDetails.name - friendly_name.current.value = appDetails.friendly_name - if (appDetails.categories) { + else if (workspace && workspace[0]) { + const workspaceDetails = allworkspaces.workspaces.find(el => el.name === atob(workspace[0])) + delete workspaceDetails['sha'] + description.current.value = workspaceDetails.description + name.current.value = workspaceDetails.name + friendly_name.current.value = workspaceDetails.friendly_name + if (workspaceDetails.categories) { let catMap = [] - appDetails.categories.map((e) => catMap.push({ + workspaceDetails.categories.map((e) => catMap.push({ label: e, value: e, })) setCategories(catMap) } - if (appDetails.architecture) { + if (workspaceDetails.architecture) { let archMap = [] - appDetails.architecture.map((e) => archMap.push({ + workspaceDetails.architecture.map((e) => archMap.push({ label: e, value: e, })) setArchitecture(archMap) } - setInlineImage('../../icons/' + appDetails.image_src) + setInlineImage('../../icons/' + workspaceDetails.image_src) - setApplication({ - ...application, - ...appDetails + setWorkspace({ + ...workspace, + ...workspaceDetails }) } - }, [app]) + }, [workspace]) - const displayApplication = () => { + const displayWorkspace = () => { return { - ...application, - // categories: JSON.stringify(application.categories) + ...workspace, + // categories: JSON.stringify(workspace.categories) } } @@ -163,23 +130,23 @@ export default function AddApp({ app }) { } useEffect(() => { - if (application && application.friendly_name) { - const updateapp = { - ...application + if (workspace && workspace.friendly_name) { + const updateWorkspace = { + ...workspace } - updateapp.image_src = friendlyUrl(updateapp.friendly_name) + '.' + ext - setApplication(updateapp) + updateWorkspace.image_src = friendlyUrl(updateWorkspace.friendly_name) + '.' + ext + setWorkspace(updateWorkspace) } }, [ext]) const updateCategories = (items) => { - const updateapp = { - ...application + const updateWorkspace = { + ...workspace } - updateapp.categories = items.map(cat => cat.value) - setApplication(updateapp) + updateWorkspace.categories = items.map(cat => cat.value) + setWorkspace(updateWorkspace) let catMap = [] - updateapp.categories.map((e) => catMap.push({ + updateWorkspace.categories.map((e) => catMap.push({ label: e, value: e, })) @@ -187,27 +154,59 @@ export default function AddApp({ app }) { } const updateArchitecture = (items) => { - const updateapp = { - ...application + const updateWorkspace = { + ...workspace } - updateapp.architecture = items.map(arch => arch.value) - setApplication(updateapp) + updateWorkspace.architecture = items.map(arch => arch.value) + setWorkspace(updateWorkspace) let archMap = [] - updateapp.architecture.map((e) => archMap.push({ + updateWorkspace.architecture.map((e) => archMap.push({ label: e, value: e, })) setArchitecture(archMap) } + function friendlyUrl(url) { + // make the url lowercase + var encodedUrl = url.toString().toLowerCase(); + // replace & with and + encodedUrl = encodedUrl.split(/\&+/).join("-and-") + // remove invalid characters + encodedUrl = encodedUrl.split(/[^a-z0-9]/).join("-"); + // remove duplicates + encodedUrl = encodedUrl.split(/-+/).join("-"); + // trim leading & trailing characters + encodedUrl = encodedUrl.trim('-'); + return encodedUrl; + } + + const downloadZip = () => { + var JSZip = require("jszip"); + const zip = new JSZip() + const folder = zip.folder(workspace.friendly_name) + folder.file('workspace.json', JSON.stringify(workspace, null, 2)) + if (icon) { + folder.file(workspace.image_src, icon.file) + } + else if (inlineImage) { + const promise = fetch(inlineImage).then(response => response.blob()) + folder.file(workspace.image_src, promise) + } + zip.generateAsync({ type: "blob" }) + .then(function (content) { + // Force down of the Zip file + saveAs(content, friendlyUrl(workspace.friendly_name) + '.zip'); + }); + } const handleChange = (event) => { - const updateapp = { - ...application + const updateWorkspace = { + ...workspace } - updateapp[event.target.name] = event.target.value + updateWorkspace[event.target.name] = event.target.value if (event.target.name === 'icon') { - delete updateapp.icon + delete updateWorkspace.icon setIcon({ value: event.target.value, file: event.target.files[0] @@ -217,11 +216,11 @@ export default function AddApp({ app }) { // return } - if (updateapp.friendly_name) { - updateapp.image_src = friendlyUrl(updateapp.friendly_name) + '.' + ext + if (updateWorkspace.friendly_name) { + updateWorkspace.image_src = friendlyUrl(updateWorkspace.friendly_name) + '.' + ext } - setApplication(updateapp) + setWorkspace(updateWorkspace) } const options = [ @@ -240,15 +239,15 @@ export default function AddApp({ app }) { return (
- Kasm Apps - + Kasm Workspaces +
-

Add Application

+

Add Workspace

-

This will help you generate the JSON file you need to upload to the App directory.

+

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.

@@ -272,7 +271,7 @@ export default function AddApp({ app }) { -

A short description about the application

+

A short description about the workspace

@@ -296,8 +295,8 @@ export default function AddApp({ app }) {
- -
{JSON.stringify(displayApplication(), null, 2)}
+ +
{JSON.stringify(displayWorkspace(), null, 2)}
@@ -307,7 +306,7 @@ export default function AddApp({ app }) { } -function App({ app, icon, inlineImage }) { +function Workspace({ workspace, icon, inlineImage }) { const [showDescription, setShowDescription] = useState(false); @@ -316,7 +315,7 @@ function App({ app, icon, inlineImage }) { if (icon) { const blob = new Blob([icon.file]) srcBlob = URL.createObjectURL(blob); - app.image_src = srcBlob + workspace.image_src = srcBlob } const installButton = () => { @@ -329,30 +328,30 @@ function App({ app, icon, inlineImage }) { return } - const appExists = false + const workspaceExists = false return (
setShowDescription(true)} className={"h-[100px] p-4 relative overflow-hidden cursor-pointer"}> - { - if ( inlineImage !== null) { e.target.src = inlineImage }}} alt={app.friendly_name} /> + { + if ( inlineImage !== null) { e.target.src = inlineImage }}} alt={workspace.friendly_name} />
-
{app.friendly_name || 'Friendly Name'}
-
{process.env.name || 'Unknown'} {official()}
+
{workspace.friendly_name || 'Friendly Name'}
+
{process.env.name || 'Manual'} {official()}
- {app.architecture && app.architecture.map((arch, index) => ( + {workspace.architecture && workspace.architecture.map((arch, index) => ( {arch} ))} - {app.categories.map((cat, index) => ( + {workspace.categories.map((cat, index) => ( {cat} ))}
- {appExists && appExists.enabled === true && appExists.available === false && ( + {workspaceExists && workspaceExists.enabled === true && workspaceExists.available === false && (
Installing
)}
@@ -360,7 +359,7 @@ function App({ app, icon, inlineImage }) { -
{app.friendly_name}
{app.description}
+
{workspace.friendly_name}
{workspace.description}
{editButton()} {installButton()} diff --git a/workspaces/README.md b/workspaces/README.md new file mode 100644 index 0000000..ebf6ecc --- /dev/null +++ b/workspaces/README.md @@ -0,0 +1,7 @@ +# Workspaces directory + +This directory is for storing all the files needed for your workspaces store, they should be stored with the following structure: + +* workspaces/Workspace Name +* workspaces/Workspace Name/workspace.json +* workspaces/Workspace Name/workspace-name.png \ No newline at end of file