|
|
|
|
@ -1,7 +1,9 @@
|
|
|
|
|
import Head from 'next/head'
|
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
|
import { useState, useEffect, useRef } from 'react'
|
|
|
|
|
import { saveAs } from 'file-saver';
|
|
|
|
|
import CreatableSelect from 'react-select/creatable';
|
|
|
|
|
import Select from 'react-select';
|
|
|
|
|
import { useRouter } from 'next/router'
|
|
|
|
|
|
|
|
|
|
export default function AddApp() {
|
|
|
|
|
|
|
|
|
|
@ -27,6 +29,10 @@ export default function AddApp() {
|
|
|
|
|
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
|
|
|
|
|
@ -34,36 +40,76 @@ export default function AddApp() {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const name = useRef(null);
|
|
|
|
|
const friendly_name = useRef(null);
|
|
|
|
|
const description = useRef(null);
|
|
|
|
|
|
|
|
|
|
const [categories, setCategories] = useState(null)
|
|
|
|
|
const [architecture, setArchitecture] = useState(null)
|
|
|
|
|
const [icon, setIcon] = useState(null)
|
|
|
|
|
const [ext, setExt] = useState('png')
|
|
|
|
|
const [inlineImage, setInlineImage] = useState(null)
|
|
|
|
|
|
|
|
|
|
const [application, setApplication] = useState({
|
|
|
|
|
friendly_name: null,
|
|
|
|
|
image_src: null,
|
|
|
|
|
description: null,
|
|
|
|
|
name: null,
|
|
|
|
|
cores: 2,
|
|
|
|
|
memory: 2768,
|
|
|
|
|
cores: 1,
|
|
|
|
|
memory: 1024,
|
|
|
|
|
gpu_count: 0,
|
|
|
|
|
cpu_allocation_method: "Inherit",
|
|
|
|
|
docker_registry: "https://index.docker.io/v1/",
|
|
|
|
|
volume_mappings: "{}",
|
|
|
|
|
run_config: "{}",
|
|
|
|
|
exec_config: "{}",
|
|
|
|
|
categories: [],
|
|
|
|
|
require_gpu: false,
|
|
|
|
|
enabled: true,
|
|
|
|
|
restrict_to_network: false,
|
|
|
|
|
restrict_network_names: "[]",
|
|
|
|
|
allow_network_selection: false,
|
|
|
|
|
notes: null,
|
|
|
|
|
image_type: 'Container',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const { app } = router.query
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetch('../../list.json')
|
|
|
|
|
.then((res) => res.json())
|
|
|
|
|
.then((apps) => {
|
|
|
|
|
if (app && app[0]) {
|
|
|
|
|
const appDetails = apps.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) {
|
|
|
|
|
let catMap = []
|
|
|
|
|
appDetails.categories.map((e) => catMap.push({
|
|
|
|
|
label: e,
|
|
|
|
|
value: e,
|
|
|
|
|
}))
|
|
|
|
|
setCategories(catMap)
|
|
|
|
|
}
|
|
|
|
|
if (appDetails.architecture) {
|
|
|
|
|
let archMap = []
|
|
|
|
|
appDetails.architecture.map((e) => archMap.push({
|
|
|
|
|
label: e,
|
|
|
|
|
value: e,
|
|
|
|
|
}))
|
|
|
|
|
setArchitecture(archMap)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInlineImage('../../icons/' + appDetails.image_src)
|
|
|
|
|
|
|
|
|
|
setApplication({
|
|
|
|
|
...application,
|
|
|
|
|
...appDetails
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}, [app])
|
|
|
|
|
|
|
|
|
|
const displayApplication = () => {
|
|
|
|
|
return {
|
|
|
|
|
...application,
|
|
|
|
|
categories: JSON.stringify(application.categories)
|
|
|
|
|
// categories: JSON.stringify(application.categories)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -98,6 +144,26 @@ export default function AddApp() {
|
|
|
|
|
}
|
|
|
|
|
updateapp.categories = items.map(cat => cat.value)
|
|
|
|
|
setApplication(updateapp)
|
|
|
|
|
let catMap = []
|
|
|
|
|
updateapp.categories.map((e) => catMap.push({
|
|
|
|
|
label: e,
|
|
|
|
|
value: e,
|
|
|
|
|
}))
|
|
|
|
|
setCategories(catMap)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateArchitecture = (items) => {
|
|
|
|
|
const updateapp = {
|
|
|
|
|
...application
|
|
|
|
|
}
|
|
|
|
|
updateapp.architecture = items.map(arch => arch.value)
|
|
|
|
|
setApplication(updateapp)
|
|
|
|
|
let archMap = []
|
|
|
|
|
updateapp.architecture.map((e) => archMap.push({
|
|
|
|
|
label: e,
|
|
|
|
|
value: e,
|
|
|
|
|
}))
|
|
|
|
|
setArchitecture(archMap)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -154,7 +220,7 @@ export default function AddApp() {
|
|
|
|
|
<p className='mb-6 opacity-70'>Select the image to use, image will be renamed when it's downloaded.</p>
|
|
|
|
|
|
|
|
|
|
<label className='mb-2 font-medium'>Friendly Name</label>
|
|
|
|
|
<input name="friendly_name" onChange={handleChange} className='mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' />
|
|
|
|
|
<input ref={friendly_name} name="friendly_name" onChange={handleChange} className='mb-2 p-2 rounded-lg bg-slate-100 border border-solid border-slate-400' />
|
|
|
|
|
<p className='mb-6 opacity-70'>This is the name that will show for users</p>
|
|
|
|
|
|
|
|
|
|
<label className='mb-2 font-medium'>Categories</label>
|
|
|
|
|
@ -164,23 +230,38 @@ export default function AddApp() {
|
|
|
|
|
options={options}
|
|
|
|
|
onChange={updateCategories}
|
|
|
|
|
styles={customStyles}
|
|
|
|
|
value={categories}
|
|
|
|
|
/>
|
|
|
|
|
<p className='mb-6 mt-2 opacity-70'>You can select from the available option or create new ones.</p>
|
|
|
|
|
|
|
|
|
|
<label className='mb-2 font-medium'>Description</label>
|
|
|
|
|
<input 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>
|
|
|
|
|
|
|
|
|
|
<label className='mb-2 font-medium'>Docker Image</label>
|
|
|
|
|
<input 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' />
|
|
|
|
|
<p className='mb-6 opacity-70'>The docker image to use, i.e. <code className='text-xs p-1 px-2 rounded bg-white/40'>kasmweb/filezilla:develop</code></p>
|
|
|
|
|
|
|
|
|
|
<label className='mb-2 font-medium'>Architecture</label>
|
|
|
|
|
<Select
|
|
|
|
|
name="architecture"
|
|
|
|
|
isMulti
|
|
|
|
|
options={[
|
|
|
|
|
{ value: 'amd64', label: 'amd64' },
|
|
|
|
|
{ value: 'arm64', label: 'arm64' },
|
|
|
|
|
]}
|
|
|
|
|
onChange={updateArchitecture}
|
|
|
|
|
styles={customStyles}
|
|
|
|
|
value={architecture}
|
|
|
|
|
/>
|
|
|
|
|
<p className='mb-6 mt-2 opacity-70'>You can select from the available option or create new ones.</p>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className='w-full lg:w-1/2 p-16 bg-slate-100'>
|
|
|
|
|
<App app={application} icon={icon} />
|
|
|
|
|
<App app={application} icon={icon} inlineImage={inlineImage} />
|
|
|
|
|
<pre className='my-8 overflow-y-auto text-xs'>{JSON.stringify(displayApplication(), null, 2)}</pre>
|
|
|
|
|
<button onClick={downloadZip} className='p-4 relative z-10 px-5 bg-emerald-600 m-2 rounded items-center text-white/70 flex cursor-pointer'>Download</button>
|
|
|
|
|
<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</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -189,7 +270,7 @@ export default function AddApp() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function App({ app, icon }) {
|
|
|
|
|
function App({ app, icon, inlineImage }) {
|
|
|
|
|
|
|
|
|
|
const [showDescription, setShowDescription] = useState(false);
|
|
|
|
|
|
|
|
|
|
@ -217,16 +298,20 @@ function App({ app, icon }) {
|
|
|
|
|
<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 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} alt={app.friendly_name} />
|
|
|
|
|
<img className="h-[90px] group-hover:scale-150 transition-all absolute left-2 top-1" src={app.image_src} onError={(e) => e.target.src = inlineImage} alt={app.friendly_name} />
|
|
|
|
|
<div className="flex-col pl-28">
|
|
|
|
|
<div className="font-bold">{app.friendly_name || 'Friendly Name'}</div>
|
|
|
|
|
<div className="text-xs mb-2 flex gap-2">{app.author || 'Unknown'} <span>{official()}</span></div>
|
|
|
|
|
<div className="text-xs mb-2 flex gap-2">{process.env.name || 'Unknown'} <span>{official()}</span></div>
|
|
|
|
|
<div className=" h-8"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="absolute bottom-0 left-0 right-0 bg-slate-400/20 h-8 text-[10px] flex items-center justify-center">
|
|
|
|
|
{app.categories.map(cat => (
|
|
|
|
|
<span className="p-2 py-0 m-[1px] inline-block rounded bg-slate-300/90">{cat}</span>
|
|
|
|
|
{app.architecture && app.architecture.map((arch, index) => (
|
|
|
|
|
<span key={'arch' + index} className="p-2 py-0 m-[1px] inline-block rounded bg-slate-400/70">{arch}</span>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
{app.categories.map((cat, index) => (
|
|
|
|
|
<span key={'cat' + index} className="p-2 py-0 m-[1px] inline-block rounded bg-slate-300/90">{cat}</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
{appExists && appExists.enabled === true && appExists.available === false && (
|