diff --git a/client/src/api/index.jsx b/client/src/api/index.jsx index 55dc28a..1d98761 100644 --- a/client/src/api/index.jsx +++ b/client/src/api/index.jsx @@ -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, diff --git a/client/src/api/market.demo.ts b/client/src/api/market.demo.ts new file mode 100644 index 0000000..d7cfc3b --- /dev/null +++ b/client/src/api/market.demo.ts @@ -0,0 +1,9 @@ +import wrap from './wrap'; + +function list() { + +} + +export default { + list, +}; \ No newline at end of file diff --git a/client/src/api/market.ts b/client/src/api/market.ts new file mode 100644 index 0000000..e2cebc7 --- /dev/null +++ b/client/src/api/market.ts @@ -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, +}; \ No newline at end of file diff --git a/client/src/menu-items/dashboard.jsx b/client/src/menu-items/dashboard.jsx index 78e424e..975e3ac 100644 --- a/client/src/menu-items/dashboard.jsx +++ b/client/src/menu-items/dashboard.jsx @@ -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 + }, ] }; diff --git a/client/src/pages/home/index.jsx b/client/src/pages/home/index.jsx index 16bc770..6c7e88f 100644 --- a/client/src/pages/home/index.jsx +++ b/client/src/pages/home/index.jsx @@ -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 ; +} + 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 - + {coStatus && !coStatus.database && ( diff --git a/client/src/pages/market/details.jsx b/client/src/pages/market/details.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pages/market/listing.jsx b/client/src/pages/market/listing.jsx new file mode 100644 index 0000000..c61a4d6 --- /dev/null +++ b/client/src/pages/market/listing.jsx @@ -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 ( + + { + screenshots.map((item, i) => ) + } + + ) +} + +function Showcases({ showcase, isDark }) { + return ( + + { + showcase.map((item, i) => ) + } + + ) +} + +function ShowcasesItem({ isDark, item }) { + return ( + + + {/* */} + + + +

{item.name}

+
+

+ + + + + + +
+
+
+ ) +} + +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 <> + + + + {openedApp && + + + + + + + + + + + + + +

{openedApp.name}

+
+ +
+ {openedApp.tags.slice(0, 8).map((tag) => )} +
+ +
+
repository: {openedApp.repository}
+
image: {openedApp.image}
+
compose: {openedApp.compose}
+
+ +
+ +
+ +
+
+
+
} + + + + + + + + + {apps && Object.keys(apps).length > 0 && apps[Object.keys(apps)[0]].map((app) => { + return +
+ + + +
{app.name}
+
{app.description}
+ +
{app.tags[0]}
+
+
+
+
+ + +
+ })} +
+
+
+ +}; + +export default MarketPage; \ No newline at end of file diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index e9965c5..865251d 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -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; diff --git a/client/src/pages/servapps/containers/logs.jsx b/client/src/pages/servapps/containers/logs.jsx index 1f99a3e..5c3269b 100644 --- a/client/src/pages/servapps/containers/logs.jsx +++ b/client/src/pages/servapps/containers/logs.jsx @@ -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'; diff --git a/client/src/routes/MainRoutes.jsx b/client/src/routes/MainRoutes.jsx index 918f40c..4595f31 100644 --- a/client/src/routes/MainRoutes.jsx +++ b/client/src/routes/MainRoutes.jsx @@ -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: , + }, + { + path: '/ui/market-listing/', + element: + }, + { + path: '/ui/market-listing/:appName', + element: } ] }; diff --git a/package-lock.json b/package-lock.json index d90786f..9400958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ac5fc92..a632fbb 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/httpServer.go b/src/httpServer.go index 8d4d6e2..7e8f724 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -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) { diff --git a/src/index.go b/src/index.go index 8098dab..971a248 100644 --- a/src/index.go +++ b/src/index.go @@ -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() { @@ -36,6 +37,8 @@ func main() { if err == nil { utils.Log("Docker API version: " + version.APIVersion) } + + market.Init() authorizationserver.Init() diff --git a/src/market/index.go b/src/market/index.go new file mode 100644 index 0000000..7081b74 --- /dev/null +++ b/src/market/index.go @@ -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 + } +} \ No newline at end of file diff --git a/src/market/init.go b/src/market/init.go new file mode 100644 index 0000000..0efb08d --- /dev/null +++ b/src/market/init.go @@ -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) + } +} \ No newline at end of file diff --git a/src/market/update.go b/src/market/update.go new file mode 100644 index 0000000..293e99f --- /dev/null +++ b/src/market/update.go @@ -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 +} \ No newline at end of file diff --git a/src/utils/types.go b/src/utils/types.go index caa72a9..c52a405 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -86,6 +86,7 @@ type Config struct { RequireMFA bool AutoUpdate bool OpenIDClients []OpenIDClient + MarketConfig MarketConfig } type HTTPConfig struct { @@ -166,4 +167,13 @@ type OpenIDClient struct { ID string `json:"id"` Secret string `json:"secret"` Redirect string `json:"redirect"` +} + +type MarketConfig struct { + Sources []MarketSource +} + +type MarketSource struct { + Name string + Url string } \ No newline at end of file diff --git a/src/utils/utils.go b/src/utils/utils.go index ecebe1c..99d3ac9 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -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()