[release] v0.7.0-unstable2

This commit is contained in:
Yann Stepienik 2023-06-09 19:48:31 +01:00
parent e69c81fe7a
commit 8d6329f8d5
19 changed files with 679 additions and 95 deletions

View file

@ -2,12 +2,14 @@ import * as _auth from './authentication';
import * as _users from './users';
import * as _config from './config';
import * as _docker from './docker';
import * as _market from './market';
import * as authDemo from './authentication.demo';
import * as usersDemo from './users.demo';
import * as configDemo from './config.demo';
import * as dockerDemo from './docker.demo';
import * as indexDemo from './index.demo';
import * as marketDemo from './market.demo';
import wrap from './wrap';
@ -101,7 +103,7 @@ let newInstall = (req, onProgress) => {
}
}
const checkHost = (host) => {
let checkHost = (host) => {
return fetch('/cosmos/api/dns-check?url=' + host, {
method: 'GET',
headers: {
@ -124,7 +126,7 @@ const checkHost = (host) => {
});
}
const getDNS = (host) => {
let getDNS = (host) => {
return fetch('/cosmos/api/dns?url=' + host, {
method: 'GET',
headers: {
@ -153,15 +155,19 @@ let auth = _auth;
let users = _users;
let config = _config;
let docker = _docker;
let market = _market;
if(isDemo) {
auth = authDemo;
users = usersDemo;
config = configDemo;
docker = dockerDemo;
market = marketDemo;
getStatus = indexDemo.getStatus;
newInstall = indexDemo.newInstall;
isOnline = indexDemo.isOnline;
checkHost = indexDemo.checkHost;
getDNS = indexDemo.getDNS;
}
export {
@ -169,6 +175,7 @@ export {
users,
config,
docker,
market,
getStatus,
newInstall,
isOnline,

View file

@ -0,0 +1,9 @@
import wrap from './wrap';
function list() {
}
export default {
list,
};

14
client/src/api/market.ts Normal file
View file

@ -0,0 +1,14 @@
import wrap from './wrap';
function list() {
return wrap(fetch('/cosmos/api/markets', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
}))
}
export {
list,
};

View file

@ -1,5 +1,5 @@
// assets
import { HomeOutlined, AppstoreOutlined, DashboardOutlined } from '@ant-design/icons';
import { HomeOutlined, AppstoreOutlined, DashboardOutlined, AppstoreAddOutlined } from '@ant-design/icons';
// icons
const icons = {
@ -29,6 +29,14 @@ const dashboard = {
icon: DashboardOutlined,
breadcrumbs: false
},
{
id: 'market',
title: 'Market',
type: 'item',
url: '/ui/market-listing',
icon: AppstoreAddOutlined,
breadcrumbs: false
},
]
};

View file

@ -12,7 +12,7 @@ import { getFullOrigin } from "../../utils/routes";
import IsLoggedIn from "../../isLoggedIn";
const HomeBackground = () => {
export const HomeBackground = () => {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
return (
@ -27,6 +27,50 @@ const HomeBackground = () => {
);
};
export const TransparentHeader = () => {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
const backColor = isDark ? '0,0,0' : '255,255,255';
const textColor = isDark ? 'white' : 'dark';
return <style>
{`header {
background: rgba(${backColor},0.35) !important;
border-bottom-color: rgba(${backColor},0.4) !important;
color: ${textColor} !important;
font-weight: bold;
}
header .MuiChip-label {
color: ${textColor} !important;
}
header .MuiButtonBase-root, header .MuiChip-colorDefault {
color: ${textColor} !important;
background: rgba(${backColor},0.5) !important;
}
.app {
transition: background 0.1s ease-in-out;
transition: transform 0.1s ease-in-out;
}
.app:hover {
cursor: pointer;
background: rgba(${backColor},0.8) !important;
transform: scale(1.05);
}
.MuiAlert-standard {
background: rgba(${backColor},0.35) !important;
color: ${textColor} !important;
font-weight: bold;
}
`}
</style>;
}
const HomePage = () => {
const { routeName } = useParams();
const [serveApps, setServeApps] = useState([]);
@ -52,9 +96,6 @@ const HomePage = () => {
background: 'rgba(255,255,255,0.35)',
}
const backColor = isDark ? '0,0,0' : '255,255,255';
const textColor = isDark ? 'white' : 'dark';
const refreshStatus = () => {
API.getStatus().then((res) => {
@ -82,41 +123,7 @@ const HomePage = () => {
return <Stack spacing={2} >
<IsLoggedIn />
<HomeBackground />
<style>
{`header {
background: rgba(${backColor},0.35) !important;
border-bottom-color: rgba(${backColor},0.4) !important;
color: ${textColor} !important;
font-weight: bold;
}
header .MuiChip-label {
color: ${textColor} !important;
}
header .MuiButtonBase-root, header .MuiChip-colorDefault {
color: ${textColor} !important;
background: rgba(${backColor},0.5) !important;
}
.app {
transition: background 0.1s ease-in-out;
transition: transform 0.1s ease-in-out;
}
.app:hover {
cursor: pointer;
background: rgba(${backColor},0.8) !important;
transform: scale(1.05);
}
.MuiAlert-standard {
background: rgba(${backColor},0.35) !important;
color: ${textColor} !important;
font-weight: bold;
}
`}
</style>
<TransparentHeader />
<Stack style={{ zIndex: 2 }} spacing={1}>
{coStatus && !coStatus.database && (
<Alert severity="error">

View file

View file

@ -0,0 +1,257 @@
import { Box, Stack } from "@mui/material";
import { HomeBackground, TransparentHeader } from "../home";
import { useEffect, useState } from "react";
import * as API from "../../api";
import { useTheme } from "@emotion/react";
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
import { useParams } from "react-router";
import Carousel from 'react-material-ui-carousel'
import { Paper, Button , Chip} from '@mui/material'
import { Link } from "react-router-dom";
import {Link as LinkMUI} from '@mui/material'
import DockerComposeImport from '../servapps/containers/docker-compose';
function Screenshots({ screenshots }) {
return (
<Carousel animation="slide" navButtonsAlwaysVisible={false} fullHeightHover="true">
{
screenshots.map((item, i) => <img key={i} src={item} width="100%" />)
}
</Carousel>
)
}
function Showcases({ showcase, isDark }) {
return (
<Carousel animation="slide" navButtonsAlwaysVisible={false} fullHeightHover="true">
{
showcase.map((item, i) => <ShowcasesItem isDark={isDark} key={i} item={item} />)
}
</Carousel>
)
}
function ShowcasesItem({ isDark, item }) {
return (
<Paper style={{
position: 'relative',
background: 'url(' + item.screenshots[0] + ')',
height: '31vh',
backgroundSize: 'auto 100%',
maxWidth: '120vh',
margin: 'auto',
}}>
<Stack direction="row" spacing={2} style={{ height: '100%', overflow: 'hidden' }} justifyContent="flex-end">
{/* <img src={item.screenshots[0]} style={{ height: '100%' }} /> */}
<Stack direction="column" spacing={2} style={{ height: '100%' }} sx={{
backgroundColor: isDark ? '#1A2027' : '#fff',
padding: '20px 100px',
width: '50%',
filter: 'drop-shadow(-20px 0px 20px rgba(0, 0, 0, 1))',
'@media (max-width: 1100px)': {
width: '70%',
padding: '20px 40px',
},
'@media (max-width: 600px)': {
width: '80%',
padding: '20px 20px',
}
}}>
<Stack direction="row" spacing={2}>
<img src={item.icon} style={{ width: '36px', height: '36px' }} />
<h2>{item.name}</h2>
</Stack>
<p dangerouslySetInnerHTML={{ __html: item.longDescription }} style={{
overflow: 'hidden',
}}></p>
<Stack direction="row" spacing={2} justifyContent="flex-start">
<Button className="CheckButton" color="primary" variant="contained">
Install
</Button>
<Link to={"/ui/market-listing/" + item.name} style={{
textDecoration: 'none',
}}>
<Button className="CheckButton" color="primary" variant="outlined">
View
</Button>
</Link>
</Stack>
</Stack>
</Stack>
</Paper>
)
}
const appCardStyle = (theme) => ({
width: '100%',
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
color: theme.palette.text.secondary,
})
const gridAnim = {
transition: 'all 0.2s ease',
opacity: 1,
transform: 'translateY(0px)',
'&.MuiGrid2-item--hidden': {
opacity: 0,
transform: 'translateY(-20px)',
},
};
const MarketPage = () => {
const [apps, setApps] = useState([]);
const [showcase, setShowcase] = useState([]);
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
const { appName } = useParams();
const backgroundStyle = isDark ? {
backgroundColor: 'rgb(0,0,0)',
// borderTop: '1px solid #595959'
} : {
backgroundColor: 'rgb(255,255,255)',
// borderTop: '1px solid rgb(220,220,220)'
};
useEffect(() => {
console.log(API.market)
API.market.list().then((res) => {
setApps(res.data.all);
setShowcase(res.data.showcase);
});
}, []);
let openedApp = null;
if (appName && Object.keys(apps).length > 0) {
openedApp = apps[Object.keys(apps)[0]].find((app) => app.name === appName);
}
return <>
<HomeBackground />
<TransparentHeader />
{openedApp && <Box style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1300,
backgroundColor: 'rgba(0,0,0,0.5)',
}}>
<Link to="/ui/market-listing" as={Box}
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}></Link>
<Stack direction="row" spacing={2} style={{ height: '100%', overflow: 'hidden' }} justifyContent="flex-end">
<Stack direction="column" spacing={3} style={{ height: '100%' }} sx={{
backgroundColor: isDark ? '#1A2027' : '#fff',
padding: '80px 80px',
width: '100%',
maxWidth: '800px',
filter: 'drop-shadow(-20px 0px 20px rgba(0, 0, 0, 1))',
'@media (max-width: 700px)': {
padding: '60px 40px',
},
'@media (max-width: 500px)': {
padding: '40px 20px',
}
}}>
<Link to="/ui/market-listing" style={{
textDecoration: 'none',
}}>
<Button className="CheckButton" color="primary" variant="outlined">
Close
</Button>
</Link>
<Screenshots screenshots={openedApp.screenshots} />
<Stack direction="row" spacing={2}>
<img src={openedApp.icon} style={{ width: '36px', height: '36px' }} />
<h2>{openedApp.name}</h2>
</Stack>
<div>
{openedApp.tags.slice(0, 8).map((tag) => <Chip label={tag} />)}
</div>
<div>
<div><strong>repository:</strong> <LinkMUI href={openedApp.repository}>{openedApp.repository}</LinkMUI></div>
<div><strong>image:</strong> <LinkMUI href={openedApp.image}>{openedApp.image}</LinkMUI></div>
<div><strong>compose:</strong> <LinkMUI href={openedApp.compose}>{openedApp.compose}</LinkMUI></div>
</div>
<div dangerouslySetInnerHTML={{ __html: openedApp.longDescription }} style={{
overflow: 'hidden',
}}></div>
<div>
<DockerComposeImport installer defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
</div>
</Stack>
</Stack>
</Box>}
<Stack style={{ position: 'relative' }} spacing={1}>
<Stack style={{ height: '35vh' }} spacing={1}>
<Showcases showcase={showcase} isDark={isDark}/>
</Stack>
<Stack spacing={1} style={{
...backgroundStyle,
marginLeft: "-24px",
marginRight: "-24px",
marginBottom: "-24px",
minHeight: 'calc(65vh - 80px)',
padding: '24px',
}}>
<Grid2 container spacing={{ xs: 1, sm: 1, md: 2 }}>
{apps && Object.keys(apps).length > 0 && apps[Object.keys(apps)[0]].map((app) => {
return <Grid2 style={{
...gridAnim,
cursor: 'pointer',
}} xs={12} sm={12} md={6} lg={4} xl={3} key={app.name} item><Link to={"/ui/market-listing/" + app.name} style={{
textDecoration: 'none',
}}>
<div key={app.name} style={appCardStyle(theme)}>
<Stack spacing={3} direction={'row'} alignItems={'center'} style={{ padding: '0px 15px' }}>
<img src={app.icon} style={{ width: 64, height: 64 }} />
<Stack spacing={1}>
<div style={{ fontWeight: "bold" }}>{app.name}</div>
<div style={{
height: '40px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'pre-wrap',
}}
>{app.description}</div>
<Stack direction={'row'} spacing={1}>
<div style={{ fontStyle: "italic", opacity: 0.7 }}>{app.tags[0]}</div>
</Stack>
</Stack>
</Stack>
</div>
</Link>
</Grid2>
})}
</Grid2>
</Stack>
</Stack>
</>
};
export default MarketPage;

View file

@ -71,12 +71,20 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
const [step, setStep] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [openModal, setOpenModal] = useState(false);
const [dockerCompose, setDockerCompose] = useState(dockerComposeInit || '');
const [dockerCompose, setDockerCompose] = useState('');
const [service, setService] = useState({});
const [ymlError, setYmlError] = useState('');
const [serviceName, setServiceName] = useState(defaultName || 'my-service');
const [hostnames, setHostnames] = useState([]);
useEffect(() => {
const text = fetch(dockerComposeInit)
.then((res) => res.text())
.then((text) => {
setDockerCompose(text);
});
}, [dockerComposeInit]);
useEffect(() => {
if(!openModal) {
return;

View file

@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { Box, Button, Checkbox, CircularProgress, Input, Stack, TextField, Typography, useMediaQuery } from '@mui/material';
import * as API from '../../../api';
import { ReactTerminal } from "react-terminal";
import LogLine from '../../../components/logLine';
import { useTheme } from '@emotion/react';

View file

@ -15,6 +15,7 @@ import ContainerIndex from '../pages/servapps/containers';
import NewDockerService from '../pages/servapps/containers/newService';
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
import OpenIdList from '../pages/openid/openid-list';
import MarketPage from '../pages/market/listing';
// render - dashboard
@ -85,6 +86,14 @@ const MainRoutes = {
{
path: '/ui/openid-manage',
element: <OpenIdList />,
},
{
path: '/ui/market-listing/',
element: <MarketPage />
},
{
path: '/ui/market-listing/:appName',
element: <MarketPage />
}
]
};

179
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "cosmos-server",
"version": "0.6.0-unstable",
"version": "0.7.0-unstable",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cosmos-server",
"version": "0.6.0-unstable",
"version": "0.7.0-unstable",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
@ -38,13 +38,13 @@
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-element-to-jsx-string": "^15.0.0",
"react-material-ui-carousel": "^3.4.2",
"react-number-format": "^4.9.4",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^8.0.4",
"react-router": "^6.4.1",
"react-router-dom": "^6.4.1",
"react-syntax-highlighter": "^15.5.0",
"react-terminal": "^1.3.1",
"react-window": "^1.8.7",
"redux": "^4.2.0",
"simplebar": "^5.3.8",
@ -3045,6 +3045,31 @@
"url": "https://opencollective.com/mui"
}
},
"node_modules/@mui/icons-material": {
"version": "5.11.16",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz",
"integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@mui/material": "^5.0.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/lab": {
"version": "5.0.0-alpha.132",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.132.tgz",
@ -6158,6 +6183,19 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/framesync": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz",
"integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/framesync/node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -8115,6 +8153,22 @@
"node": ">=10.13.0"
}
},
"node_modules/popmotion": {
"version": "9.3.6",
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz",
"integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==",
"dependencies": {
"framesync": "5.3.0",
"hey-listen": "^1.0.8",
"style-value-types": "4.1.4",
"tslib": "^2.1.0"
}
},
"node_modules/popmotion/node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
@ -8412,6 +8466,67 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-material-ui-carousel": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-3.4.2.tgz",
"integrity": "sha512-jUbC5aBWqbbbUOOdUe3zTVf4kMiZFwKJqwhxzHgBfklaXQbSopis4iWAHvEOLcZtSIJk4JAGxKE0CmxDoxvUuw==",
"dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.4.1",
"@mui/material": "^5.4.1",
"@mui/system": "^5.4.1",
"framer-motion": "^4.1.17"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@mui/icons-material": "^5.0.0",
"@mui/material": "^5.0.0",
"@mui/system": "^5.0.0",
"react": "^17.0.1 || ^18.0.0",
"react-dom": "^17.0.2 || ^18.0.0"
}
},
"node_modules/react-material-ui-carousel/node_modules/@emotion/is-prop-valid": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
"optional": true,
"dependencies": {
"@emotion/memoize": "0.7.4"
}
},
"node_modules/react-material-ui-carousel/node_modules/@emotion/memoize": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
"optional": true
},
"node_modules/react-material-ui-carousel/node_modules/framer-motion": {
"version": "4.1.17",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz",
"integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==",
"dependencies": {
"framesync": "5.3.0",
"hey-listen": "^1.0.8",
"popmotion": "9.3.6",
"style-value-types": "4.1.4",
"tslib": "^2.1.0"
},
"optionalDependencies": {
"@emotion/is-prop-valid": "^0.8.2"
},
"peerDependencies": {
"react": ">=16.8 || ^17.0.0",
"react-dom": ">=16.8 || ^17.0.0"
}
},
"node_modules/react-material-ui-carousel/node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/react-number-format": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.9.4.tgz",
@ -8532,50 +8647,6 @@
"react": ">= 0.14.0"
}
},
"node_modules/react-terminal": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/react-terminal/-/react-terminal-1.3.1.tgz",
"integrity": "sha512-lbkrih1be0nlJptZR7uwV6YF8PMuxKJOKhGN+GVuFKp9dY/qpSYF76KMGZZJnWbbxAt5Bkf+aUt4iyy5F8NBdQ==",
"dependencies": {
"prop-types": "^15.7.2",
"react-device-detect": "2.1.2"
},
"peerDependencies": {
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"node_modules/react-terminal/node_modules/react-device-detect": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-2.1.2.tgz",
"integrity": "sha512-N42xttwez3ECgu4KpOL2ICesdfoz8NCBfmc1rH9FRYSjH7NmMyANPSrQ3EvAtJyj/6TzJNhrANSO38iXjCB2Ug==",
"dependencies": {
"ua-parser-js": "^0.7.30"
},
"peerDependencies": {
"react": ">= 0.14.0 < 18.0.0",
"react-dom": ">= 0.14.0 < 18.0.0"
}
},
"node_modules/react-terminal/node_modules/ua-parser-js": {
"version": "0.7.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz",
"integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -9268,6 +9339,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-value-types": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz",
"integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==",
"dependencies": {
"hey-listen": "^1.0.8",
"tslib": "^2.1.0"
}
},
"node_modules/style-value-types/node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.7.0-unstable",
"version": "0.7.0-unstable2",
"description": "",
"main": "test-server.js",
"bugs": {
@ -38,13 +38,13 @@
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-element-to-jsx-string": "^15.0.0",
"react-material-ui-carousel": "^3.4.2",
"react-number-format": "^4.9.4",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^8.0.4",
"react-router": "^6.4.1",
"react-router-dom": "^6.4.1",
"react-syntax-highlighter": "^15.5.0",
"react-terminal": "^1.3.1",
"react-window": "^1.8.7",
"redux": "^4.2.0",
"simplebar": "^5.3.8",

View file

@ -8,6 +8,7 @@ import (
"github.com/azukaar/cosmos-server/src/proxy"
"github.com/azukaar/cosmos-server/src/docker"
"github.com/azukaar/cosmos-server/src/authorizationserver"
"github.com/azukaar/cosmos-server/src/market"
"github.com/gorilla/mux"
"strconv"
"time"
@ -277,9 +278,9 @@ func StartServer() {
srapi.HandleFunc("/api/servapps/{containerId}/networks", docker.NetworkContainerRoutes)
srapi.HandleFunc("/api/servapps/{containerId}/check-update", docker.CanUpdateImageRoute)
srapi.HandleFunc("/api/servapps", docker.ContainersRoute)
srapi.HandleFunc("/api/docker-service", docker.CreateServiceRoute)
srapi.HandleFunc("/api/markets", market.MarketGet)
if(!config.HTTPConfig.AcceptAllInsecureHostname) {

View file

@ -8,6 +8,7 @@ import (
"github.com/azukaar/cosmos-server/src/docker"
"github.com/azukaar/cosmos-server/src/utils"
"github.com/azukaar/cosmos-server/src/authorizationserver"
"github.com/azukaar/cosmos-server/src/market"
)
func main() {
@ -37,6 +38,8 @@ func main() {
utils.Log("Docker API version: " + version.APIVersion)
}
market.Init()
authorizationserver.Init()
StartServer()

56
src/market/index.go Normal file
View file

@ -0,0 +1,56 @@
package market
import (
"net/http"
"encoding/json"
"github.com/azukaar/cosmos-server/src/utils"
)
type marketGetResult struct {
Showcase []appDefinition `json:"showcase"`
All map[string]interface{} `json:"all"`
}
func MarketGet(w http.ResponseWriter, req *http.Request) {
if utils.AdminOnly(w, req) != nil {
return
}
if(req.Method == "GET") {
err := updateCache(w, req)
if err != nil {
return
}
// return the first 10 results of each market
marketGetResult := marketGetResult{
All: make(map[string]interface{}),
Showcase: []appDefinition{},
}
for _, market := range currentMarketcache {
results := []appDefinition{}
for _, app := range market.Results.All {
// if i < 10 {
results = append(results, app)
// } else {
// break
// }
}
marketGetResult.All[market.Name] = results
}
if len(currentMarketcache) > 0 {
marketGetResult.Showcase = currentMarketcache[0].Results.Showcase
}
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "OK",
"data": marketGetResult,
})
} else {
utils.Error("MarketGet: Method not allowed" + req.Method, nil)
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
return
}
}

20
src/market/init.go Normal file
View file

@ -0,0 +1,20 @@
package market
import (
"github.com/azukaar/cosmos-server/src/utils"
)
func Init() {
config := utils.GetMainConfig()
currentMarketcache = []marketCacheObject{}
for _, marketDef := range config.MarketConfig.Sources {
market := marketCacheObject{
Url: marketDef.Url,
Name: marketDef.Name,
}
currentMarketcache = append(currentMarketcache, market)
utils.Log("MarketInit: Added market " + market.Name)
}
}

74
src/market/update.go Normal file
View file

@ -0,0 +1,74 @@
package market
import (
"net/http"
"encoding/json"
"github.com/azukaar/cosmos-server/src/utils"
"time"
)
type appDefinition struct {
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
LongDescription string `json:"longDescription"`
Tags []string `json:"tags"`
Repository string `json:"repository"`
Image string `json:"image"`
Screenshots []string `json:"screenshots"`
Icon string `json:"icon"`
Compose string `json:"compose"`
}
type marketDefinition struct {
Showcase []appDefinition `json:"showcase"`
All []appDefinition `json:"all"`
Source string `json:"source"`
}
type marketCacheObject struct {
Url string `json:"url"`
Name string `json:"name"`
LastUpdate time.Time `json:"lastUpdate"`
Results marketDefinition `json:"results"`
}
var currentMarketcache []marketCacheObject
func updateCache(w http.ResponseWriter, req *http.Request) error {
for index, cachedMarket := range currentMarketcache {
if cachedMarket.LastUpdate.Add(time.Hour * 12).Before(time.Now()) {
utils.Log("MarketUpdate: Updating market " + cachedMarket.Name)
// fetch market.url
resp, err := http.Get(cachedMarket.Url)
if err != nil {
utils.Error("MarketUpdate: Error while fetching market" + cachedMarket.Url, err)
utils.HTTPError(w, "Market Get Error " + cachedMarket.Url, http.StatusInternalServerError, "MK001")
return err
}
defer resp.Body.Close()
// parse body
var result marketDefinition
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
utils.Error("MarketUpdate: Error while parsing market" + cachedMarket.Url, err)
utils.HTTPError(w, "Market Get Error " + cachedMarket.Url, http.StatusInternalServerError, "MK003")
return err
}
cachedMarket.Results = result
cachedMarket.LastUpdate = time.Now()
utils.Log("MarketUpdate: Updated market " + result.Source + " with " + string(len(result.All)) + " results")
// save to cache
currentMarketcache[index] = cachedMarket
}
}
return nil
}

View file

@ -86,6 +86,7 @@ type Config struct {
RequireMFA bool
AutoUpdate bool
OpenIDClients []OpenIDClient
MarketConfig MarketConfig
}
type HTTPConfig struct {
@ -167,3 +168,12 @@ type OpenIDClient struct {
Secret string `json:"secret"`
Redirect string `json:"redirect"`
}
type MarketConfig struct {
Sources []MarketSource
}
type MarketSource struct {
Name string
Url string
}

View file

@ -78,6 +78,14 @@ var DefaultConfig = Config{
Routes: []ProxyRouteConfig{},
},
},
MarketConfig: MarketConfig{
Sources: []MarketSource{
MarketSource{
Url: "https://cosmos-cloud.io/repository",
Name: "Cosmos Cloud",
},
},
},
}
func FileExists(path string) bool {
@ -166,6 +174,16 @@ func ReadConfigFromFile() Config {
Fatal("Reading Config File: " + errString, err)
}
// check if outdated
if len(config.MarketConfig.Sources) == 0 {
config.MarketConfig.Sources = []MarketSource{
MarketSource{
Url: "https://cosmos-cloud.io/repository",
Name: "Cosmos Cloud",
},
}
}
return config
}
@ -243,7 +261,6 @@ func GetConfigFileName() string {
return configFile
}
func CreateDefaultConfigFileIfNecessary() bool {
configFile := GetConfigFileName()