[release] v0.7.0-unstable2
This commit is contained in:
parent
e69c81fe7a
commit
8d6329f8d5
|
@ -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,
|
||||
|
|
9
client/src/api/market.demo.ts
Normal file
9
client/src/api/market.demo.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function list() {
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
list,
|
||||
};
|
14
client/src/api/market.ts
Normal file
14
client/src/api/market.ts
Normal 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,
|
||||
};
|
|
@ -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
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
0
client/src/pages/market/details.jsx
Normal file
0
client/src/pages/market/details.jsx
Normal file
257
client/src/pages/market/listing.jsx
Normal file
257
client/src/pages/market/listing.jsx
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
179
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
56
src/market/index.go
Normal 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
20
src/market/init.go
Normal 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
74
src/market/update.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in a new issue