@ -4,17 +4,17 @@ import { saveAs } from 'file-saver';
import CreatableSelect from 'react-select/creatable' ;
import CreatableSelect from 'react-select/creatable' ;
import Select from 'react-select' ;
import Select from 'react-select' ;
import { useRouter } from 'next/router'
import { useRouter } from 'next/router'
import all app s from '../../../public/list.json'
import all workspace s from '../../../public/list.json'
export async function getStaticPaths ( ) {
export async function getStaticPaths ( ) {
let paths = all apps. apps . map ( app => ( {
let paths = all workspaces. workspaces . map ( workspace => ( {
params : {
params : {
app: [ btoa ( app . name ) ]
workspace: [ btoa ( workspace . name ) ]
}
}
} ) )
} ) )
paths . push ( {
paths . push ( {
params : { app : null }
params : { workspace : null }
} )
} )
return {
return {
paths ,
paths ,
@ -24,47 +24,14 @@ export async function getStaticPaths() {
// `getStaticPaths` requires using `getStaticProps`
// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps ( { params } ) {
export async function getStaticProps ( { params } ) {
const app = params . app
const workspace = params . workspace
return {
return {
// Passed to the page component as props
// Passed to the page component as props
props : { app: app ? ? null } ,
props : { workspace: workspace ? ? null } ,
}
}
}
}
export default function AddApp ( { app } ) {
export default function New ( { workspace } ) {
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' ) ;
} ) ;
}
const name = useRef ( null ) ;
const name = useRef ( null ) ;
const friendly _name = useRef ( null ) ;
const friendly _name = useRef ( null ) ;
@ -92,58 +59,58 @@ export default function AddApp({ app }) {
image _type : 'Container' ,
image _type : 'Container' ,
}
}
const [ application, setApplication ] = useState ( defaultState )
const [ workspace, setWorkspace ] = useState ( defaultState )
const router = useRouter ( )
const router = useRouter ( )
// const { app } = router.query
// const { workspace } = router.query
useEffect ( ( ) => {
useEffect ( ( ) => {
console . log ( app )
console . log ( workspace )
if ( app === null ) {
if ( workspace === null ) {
description . current . value = ''
description . current . value = ''
name . current . value = ''
name . current . value = ''
friendly _name . current . value = ''
friendly _name . current . value = ''
setCategories ( null )
setCategories ( null )
setArchitecture ( null )
setArchitecture ( null )
setIcon ( null )
setIcon ( null )
set Application ( defaultState )
set Workspace ( defaultState )
}
}
else if ( app && app [ 0 ] ) {
else if ( workspace && workspace [ 0 ] ) {
const appDetails = allapps . app s. find ( el => el . name === atob ( app [ 0 ] ) )
const workspaceDetails = allworkspaces . workspace s. find ( el => el . name === atob ( workspace [ 0 ] ) )
delete app Details[ 'sha' ]
delete workspace Details[ 'sha' ]
description . current . value = app Details. description
description . current . value = workspace Details. description
name . current . value = app Details. name
name . current . value = workspace Details. name
friendly _name . current . value = app Details. friendly _name
friendly _name . current . value = workspace Details. friendly _name
if ( app Details. categories ) {
if ( workspace Details. categories ) {
let catMap = [ ]
let catMap = [ ]
app Details. categories . map ( ( e ) => catMap . push ( {
workspace Details. categories . map ( ( e ) => catMap . push ( {
label : e ,
label : e ,
value : e ,
value : e ,
} ) )
} ) )
setCategories ( catMap )
setCategories ( catMap )
}
}
if ( app Details. architecture ) {
if ( workspace Details. architecture ) {
let archMap = [ ]
let archMap = [ ]
app Details. architecture . map ( ( e ) => archMap . push ( {
workspace Details. architecture . map ( ( e ) => archMap . push ( {
label : e ,
label : e ,
value : e ,
value : e ,
} ) )
} ) )
setArchitecture ( archMap )
setArchitecture ( archMap )
}
}
setInlineImage ( '../../icons/' + app Details. image _src )
setInlineImage ( '../../icons/' + workspace Details. image _src )
set Application ( {
set Workspace ( {
... application ,
... workspace ,
... app Details
... workspace Details
} )
} )
}
}
} , [ app ] )
} , [ workspace ] )
const display Application = ( ) => {
const display Workspace = ( ) => {
return {
return {
... application ,
... workspace ,
// categories: JSON.stringify( application .categories)
// categories: JSON.stringify( workspace .categories)
}
}
}
}
@ -163,23 +130,23 @@ export default function AddApp({ app }) {
}
}
useEffect ( ( ) => {
useEffect ( ( ) => {
if ( application && application . friendly _name ) {
if ( workspace && workspace . friendly _name ) {
const update app = {
const update Workspace = {
... application
... workspace
}
}
update app. image _src = friendlyUrl ( updateapp . friendly _name ) + '.' + ext
update Workspace. image _src = friendlyUrl ( updateWorkspace . friendly _name ) + '.' + ext
set Application( updateapp )
set Workspace( updateWorkspace )
}
}
} , [ ext ] )
} , [ ext ] )
const updateCategories = ( items ) => {
const updateCategories = ( items ) => {
const update app = {
const update Workspace = {
... application
... workspace
}
}
update app . categories = items . map ( cat => cat . value )
update Workspace . categories = items . map ( cat => cat . value )
set Application( updateapp )
set Workspace( updateWorkspace )
let catMap = [ ]
let catMap = [ ]
update app . categories . map ( ( e ) => catMap . push ( {
update Workspace . categories . map ( ( e ) => catMap . push ( {
label : e ,
label : e ,
value : e ,
value : e ,
} ) )
} ) )
@ -187,27 +154,59 @@ export default function AddApp({ app }) {
}
}
const updateArchitecture = ( items ) => {
const updateArchitecture = ( items ) => {
const update app = {
const update Workspace = {
... application
... workspace
}
}
update app . architecture = items . map ( arch => arch . value )
update Workspace . architecture = items . map ( arch => arch . value )
set Application( updateapp )
set Workspace( updateWorkspace )
let archMap = [ ]
let archMap = [ ]
update app . architecture . map ( ( e ) => archMap . push ( {
update Workspace . architecture . map ( ( e ) => archMap . push ( {
label : e ,
label : e ,
value : e ,
value : e ,
} ) )
} ) )
setArchitecture ( archMap )
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 handleChange = ( event ) => {
const updateapp = {
const update Workspace = {
... application
... workspace
}
}
updateapp [ event . target . name ] = event . target . value
update Workspace [ event . target . name ] = event . target . value
if ( event . target . name === 'icon' ) {
if ( event . target . name === 'icon' ) {
delete updateapp . icon
delete update Workspace . icon
setIcon ( {
setIcon ( {
value : event . target . value ,
value : event . target . value ,
file : event . target . files [ 0 ]
file : event . target . files [ 0 ]
@ -217,11 +216,11 @@ export default function AddApp({ app }) {
// return
// return
}
}
if ( update app . friendly _name ) {
if ( update Workspace . friendly _name ) {
update app. image _src = friendlyUrl ( updateapp . friendly _name ) + '.' + ext
update Workspace. image _src = friendlyUrl ( updateWorkspace . friendly _name ) + '.' + ext
}
}
set Application( updateapp )
set Workspace( updateWorkspace )
}
}
const options = [
const options = [
@ -240,15 +239,15 @@ export default function AddApp({ app }) {
return (
return (
< div className = "" >
< div className = "" >
< Head >
< Head >
< title > Kasm App s< / t i t l e >
< title > Kasm Workspace s< / t i t l e >
< meta name = "description" content = "List of app s for Kasm Webspaces" / >
< meta name = "description" content = "List of workspace s for Kasm Webspaces" / >
< link rel = "icon" href = "/favicon.ico" / >
< link rel = "icon" href = "/favicon.ico" / >
< / H e a d >
< / H e a d >
< div className = 'flex flex-col lg:flex-row w-full my-20 max-w-6xl text-sm rounded-xl overflow-hidden mx-auto' >
< div className = 'flex flex-col lg:flex-row w-full my-20 max-w-6xl text-sm rounded-xl overflow-hidden mx-auto' >
< div className = 'w-full lg:w-1/2 p-16 bg-slate-300' >
< div className = 'w-full lg:w-1/2 p-16 bg-slate-300' >
< h1 className = 'text-2xl font-medium mb-2' > Add Application < / h 1 >
< h1 className = 'text-2xl font-medium mb-2' > Add Workspace < / h 1 >
< div className = 'flex flex-col' >
< div className = 'flex flex-col' >
< p className = 'mb-8 opacity-70' > This will help you generate the JSON file you need to upload to the App directory . < / p >
< p className = 'mb-8 opacity-70' > 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 . < / p >
< label className = 'mb-2 font-medium' > Icon < / l a b e l >
< label className = 'mb-2 font-medium' > Icon < / l a b e l >
< input type = "file" name = "icon" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
< input type = "file" name = "icon" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
@ -272,7 +271,7 @@ export default function AddApp({ app }) {
< label className = 'mb-2 font-medium' > Description < / l a b e l >
< label className = 'mb-2 font-medium' > Description < / l a b e l >
< input ref = { description } name = "description" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
< input ref = { description } name = "description" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
< p className = 'mb-6 opacity-70' > A short description about the application < / p >
< p className = 'mb-6 opacity-70' > A short description about the workspace < / p >
< label className = 'mb-2 font-medium' > Docker Image < / l a b e l >
< label className = 'mb-2 font-medium' > Docker Image < / l a b e l >
< input ref = { name } name = "name" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
< input ref = { name } name = "name" onChange = { handleChange } className = 'mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' / >
@ -296,8 +295,8 @@ export default function AddApp({ app }) {
< / d i v >
< / d i v >
< / d i v >
< / d i v >
< div className = 'w-full lg:w-1/2 p-16 bg-slate-100' >
< div className = 'w-full lg:w-1/2 p-16 bg-slate-100' >
< App app = { application } icon = { icon } inlineImage = { inlineImage } / >
< Workspace workspace = { workspace } icon = { icon } inlineImage = { inlineImage } / >
< pre className = 'my-8 overflow-y-auto text-xs' > { JSON . stringify ( display Application ( ) , null , 2 ) } < / p r e >
< pre className = 'my-8 overflow-y-auto text-xs' > { JSON . stringify ( display Workspace ( ) , null , 2 ) } < / p r e >
< button onClick = { downloadZip } className = 'p-4 relative z-10 px-5 bg-cyan-700 border-t border-white/20 border-solid hover:bg-slate-900 transition m-2 rounded items-center text-white/70 flex cursor-pointer' > Download < / b u t t o n >
< button onClick = { downloadZip } className = 'p-4 relative z-10 px-5 bg-cyan-700 border-t border-white/20 border-solid hover:bg-slate-900 transition m-2 rounded items-center text-white/70 flex cursor-pointer' > Download < / b u t t o n >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
@ -307,7 +306,7 @@ export default function AddApp({ app }) {
}
}
function App( { app , icon , inlineImage } ) {
function Workspace( { workspace , icon , inlineImage } ) {
const [ showDescription , setShowDescription ] = useState ( false ) ;
const [ showDescription , setShowDescription ] = useState ( false ) ;
@ -316,7 +315,7 @@ function App({ app, icon, inlineImage }) {
if ( icon ) {
if ( icon ) {
const blob = new Blob ( [ icon . file ] )
const blob = new Blob ( [ icon . file ] )
srcBlob = URL . createObjectURL ( blob ) ;
srcBlob = URL . createObjectURL ( blob ) ;
app . image _src = srcBlob
workspace . image _src = srcBlob
}
}
const installButton = ( ) => {
const installButton = ( ) => {
@ -329,30 +328,30 @@ function App({ app, icon, inlineImage }) {
return
return
}
}
const app Exists = false
const workspace Exists = false
return (
return (
< div className = { "rounded-xl group w-full shadow max-w-xs relative overflow-hidden h-[100px] border border-solid flex flex-col justify-between bg-slate-300 border-slate-400/50" } >
< div className = { "rounded-xl group w-full shadow max-w-xs relative overflow-hidden h-[100px] border border-solid flex flex-col justify-between bg-slate-300 border-slate-400/50" } >
< div className = { "absolute top-0 left-0 right-0 h-[200px] transition-all" + ( showDescription ? ' -translate-y-1/2' : '' ) } >
< div className = { "absolute top-0 left-0 right-0 h-[200px] transition-all" + ( showDescription ? ' -translate-y-1/2' : '' ) } >
< div onClick = { ( ) => setShowDescription ( true ) } className = { "h-[100px] p-4 relative overflow-hidden cursor-pointer" } >
< div onClick = { ( ) => setShowDescription ( true ) } className = { "h-[100px] p-4 relative overflow-hidden cursor-pointer" } >
< img className = "h-[90px] group-hover:scale-150 transition-all absolute left-2 top-1" src = { app . image _src } onError = { ( e ) => {
< img className = "h-[90px] group-hover:scale-150 transition-all absolute left-2 top-1" src = { workspace . image _src } onError = { ( e ) => {
if ( inlineImage !== null ) { e . target . src = inlineImage } } } alt = { app . friendly _name } / >
if ( inlineImage !== null ) { e . target . src = inlineImage } } } alt = { workspace . friendly _name } / >
< div className = "flex-col pl-28" >
< div className = "flex-col pl-28" >
< div className = "font-bold" > { app . friendly _name || 'Friendly Name' } < / d i v >
< div className = "font-bold" > { workspace . friendly _name || 'Friendly Name' } < / d i v >
< div className = "text-xs mb-2 flex gap-2" > { process . env . name || ' Unknown '} < span > { official ( ) } < / s p a n > < / d i v >
< div className = "text-xs mb-2 flex gap-2" > { process . env . name || ' Manual '} < span > { official ( ) } < / s p a n > < / d i v >
< div className = " h-8" > < / d i v >
< div className = " h-8" > < / d i v >
< / d i v >
< / d i v >
< div className = "absolute bottom-0 left-0 right-0 bg-slate-400/20 h-8 text-[10px] flex items-center justify-center" >
< div className = "absolute bottom-0 left-0 right-0 bg-slate-400/20 h-8 text-[10px] flex items-center justify-center" >
{ app. architecture && app . architecture . map ( ( arch , index ) => (
{ workspace. architecture && workspace . architecture . map ( ( arch , index ) => (
< span key = { 'arch' + index } className = "p-2 py-0 m-[1px] inline-block rounded bg-slate-400/70" > { arch } < / s p a n >
< span key = { 'arch' + index } className = "p-2 py-0 m-[1px] inline-block rounded bg-slate-400/70" > { arch } < / s p a n >
) ) }
) ) }
{ app . categories . map ( ( cat , index ) => (
{ workspace . categories . map ( ( cat , index ) => (
< span key = { 'cat' + index } className = "p-2 py-0 m-[1px] inline-block rounded bg-slate-300/90" > { cat } < / s p a n >
< span key = { 'cat' + index } className = "p-2 py-0 m-[1px] inline-block rounded bg-slate-300/90" > { cat } < / s p a n >
) ) }
) ) }
< / d i v >
< / d i v >
{ appExists && appExists . enabled === true && app Exists. available === false && (
{ workspaceExists && workspaceExists . enabled === true && workspace Exists. available === false && (
< div className = "absolute inset-0 flex justify-center items-center bg-slate-600/70 text-white" > < i className = "fa fa-spinner fa-spin mr-3" > < / i > I n s t a l l i n g < / d i v >
< div className = "absolute inset-0 flex justify-center items-center bg-slate-600/70 text-white" > < i className = "fa fa-spinner fa-spin mr-3" > < / i > I n s t a l l i n g < / d i v >
) }
) }
< / d i v >
< / d i v >
@ -360,7 +359,7 @@ function App({ app, icon, inlineImage }) {
< button className = "absolute right-2 top-2 bg-slate-100 rounded-full flex justify-center items-center h-6 w-6" onClick = { ( ) => setShowDescription ( false ) } >
< button className = "absolute right-2 top-2 bg-slate-100 rounded-full flex justify-center items-center h-6 w-6" onClick = { ( ) => setShowDescription ( false ) } >
< svg style = { { height : '14px' } } xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 320 512" > < path d = "M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" / > < / s v g >
< svg style = { { height : '14px' } } xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 320 512" > < path d = "M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" / > < / s v g >
< / b u t t o n >
< / b u t t o n >
< div className = "flex flex-col flex-grow" > < div className = "font-bold" > { app . friendly _name } < / d i v > { ap p . d e s c r i p t i o n } < / d i v >
< div className = "flex flex-col flex-grow" > < div className = "font-bold" > { workspace . friendly _name } < / d i v > { wo r k s p a c e . d e s c r i p t i o n } < / d i v >
< div className = "flex flex-col justify-end gap-1" >
< div className = "flex flex-col justify-end gap-1" >
{ editButton ( ) }
{ editButton ( ) }
{ installButton ( ) }
{ installButton ( ) }