mirror of
https://github.com/Websoft9/websoft9.git
synced 2024-11-22 07:30:24 +00:00
update plugins
This commit is contained in:
parent
bfee7c5a47
commit
3bebd8b680
32 changed files with 416 additions and 149 deletions
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "./static/css/main.751babb1.css",
|
"main.css": "./static/css/main.751babb1.css",
|
||||||
"main.js": "./static/js/main.a27efeeb.js",
|
"main.js": "./static/js/main.8d3e5d4b.js",
|
||||||
"static/js/688.bf21350d.chunk.js": "./static/js/688.bf21350d.chunk.js",
|
"static/js/688.694c9b06.chunk.js": "./static/js/688.694c9b06.chunk.js",
|
||||||
"static/js/376.0505e571.chunk.js": "./static/js/376.0505e571.chunk.js",
|
"static/js/376.0505e571.chunk.js": "./static/js/376.0505e571.chunk.js",
|
||||||
"static/js/426.910887ac.chunk.js": "./static/js/426.910887ac.chunk.js",
|
"static/js/426.910887ac.chunk.js": "./static/js/426.910887ac.chunk.js",
|
||||||
"static/js/912.833f32c9.chunk.js": "./static/js/912.833f32c9.chunk.js",
|
"static/js/912.833f32c9.chunk.js": "./static/js/912.833f32c9.chunk.js",
|
||||||
|
@ -43,8 +43,8 @@
|
||||||
"static/media/logo-sm.svg": "./static/media/logo-sm.53b8ca70620b0b2968874a3660f195dd.svg",
|
"static/media/logo-sm.svg": "./static/media/logo-sm.53b8ca70620b0b2968874a3660f195dd.svg",
|
||||||
"index.html": "./index.html",
|
"index.html": "./index.html",
|
||||||
"main.751babb1.css.map": "./static/css/main.751babb1.css.map",
|
"main.751babb1.css.map": "./static/css/main.751babb1.css.map",
|
||||||
"main.a27efeeb.js.map": "./static/js/main.a27efeeb.js.map",
|
"main.8d3e5d4b.js.map": "./static/js/main.8d3e5d4b.js.map",
|
||||||
"688.bf21350d.chunk.js.map": "./static/js/688.bf21350d.chunk.js.map",
|
"688.694c9b06.chunk.js.map": "./static/js/688.694c9b06.chunk.js.map",
|
||||||
"376.0505e571.chunk.js.map": "./static/js/376.0505e571.chunk.js.map",
|
"376.0505e571.chunk.js.map": "./static/js/376.0505e571.chunk.js.map",
|
||||||
"426.910887ac.chunk.js.map": "./static/js/426.910887ac.chunk.js.map",
|
"426.910887ac.chunk.js.map": "./static/js/426.910887ac.chunk.js.map",
|
||||||
"912.833f32c9.chunk.js.map": "./static/js/912.833f32c9.chunk.js.map",
|
"912.833f32c9.chunk.js.map": "./static/js/912.833f32c9.chunk.js.map",
|
||||||
|
@ -53,6 +53,6 @@
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.751babb1.css",
|
"static/css/main.751babb1.css",
|
||||||
"static/js/main.a27efeeb.js"
|
"static/js/main.8d3e5d4b.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><script type="text/javascript" src="../base1/cockpit.js"></script><script type="text/javascript" src="../*/po.js"></script><link rel="manifest" href="./manifest.json"/><title>App Store</title><script defer="defer" src="./static/js/main.a27efeeb.js"></script><link href="./static/css/main.751babb1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" style="height:100%"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><script type="text/javascript" src="../base1/cockpit.js"></script><script type="text/javascript" src="../*/po.js"></script><link rel="manifest" href="./manifest.json"/><title>App Store</title><script defer="defer" src="./static/js/main.8d3e5d4b.js"></script><link href="./static/css/main.751babb1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" style="height:100%"></div></body></html>
|
2
cockpit/appstore/build/static/js/688.694c9b06.chunk.js
Normal file
2
cockpit/appstore/build/static/js/688.694c9b06.chunk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -26,6 +26,7 @@ const getContentfulData = gql`
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
key
|
key
|
||||||
|
hot
|
||||||
trademark
|
trademark
|
||||||
summary
|
summary
|
||||||
overview
|
overview
|
||||||
|
@ -39,11 +40,11 @@ const getContentfulData = gql`
|
||||||
logo {
|
logo {
|
||||||
imageurl
|
imageurl
|
||||||
}
|
}
|
||||||
catalogCollection(limit:20) {
|
catalogCollection(limit:15) {
|
||||||
items {
|
items {
|
||||||
key
|
key
|
||||||
title
|
title
|
||||||
catalogCollection(limit:1){
|
catalogCollection(limit:5){
|
||||||
items{
|
items{
|
||||||
key
|
key
|
||||||
title
|
title
|
||||||
|
@ -58,12 +59,14 @@ const getContentfulData = gql`
|
||||||
catalogCollection(limit:20) {
|
catalogCollection(limit:20) {
|
||||||
items {
|
items {
|
||||||
key
|
key
|
||||||
|
position
|
||||||
title
|
title
|
||||||
linkedFrom(allowedLocales:["en-US"]) {
|
linkedFrom(allowedLocales:["en-US"]) {
|
||||||
catalogCollection(limit:20) {
|
catalogCollection(limit:20) {
|
||||||
items {
|
items {
|
||||||
key
|
key
|
||||||
title
|
title
|
||||||
|
position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,8 +304,18 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mainCatalogs = allData?.catalog.linkedFrom.catalogCollection.items; //主目录数据
|
//主目录数据
|
||||||
//const apps = allData?.productCollection?.items;//所有应用数据
|
const mainCatalogs = allData?.catalog.linkedFrom.catalogCollection.items?.sort(function (a, b) {
|
||||||
|
if (a.position === null && b.position === null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.position === null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.position === null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.position - b.position;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [apps, setApps] = useState(null); //用于存储通过目录筛选出来的数据
|
const [apps, setApps] = useState(null); //用于存储通过目录筛选出来的数据
|
||||||
const [appList, setAppList] = useState(null); //用于存储通过目录筛选出来的数据
|
const [appList, setAppList] = useState(null); //用于存储通过目录筛选出来的数据
|
||||||
|
@ -314,8 +327,20 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
||||||
skipCount += allData.productCollection.items.length;
|
skipCount += allData.productCollection.items.length;
|
||||||
// 调用fetchMoreProducts函数来获取更多的产品,如果有的话
|
// 调用fetchMoreProducts函数来获取更多的产品,如果有的话
|
||||||
fetchMoreProducts();
|
fetchMoreProducts();
|
||||||
setAppList(allData.productCollection?.items);
|
//对产品根据hot排序:降序
|
||||||
setApps(allData.productCollection?.items);
|
const data = allData.productCollection?.items?.sort(function (a, b) {
|
||||||
|
if (a.hot === null && b.hot === null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.hot === null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.hot === null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return b.hot - a.hot;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setAppList(data);
|
||||||
|
setApps(data);
|
||||||
}
|
}
|
||||||
}, [allData])
|
}, [allData])
|
||||||
|
|
||||||
|
@ -345,7 +370,18 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
||||||
selectedMainCatalog === 'All'
|
selectedMainCatalog === 'All'
|
||||||
? []
|
? []
|
||||||
: mainCatalogs.filter(c => c.key === selectedMainCatalog)?.[0]?.linkedFrom?.catalogCollection?.items;
|
: mainCatalogs.filter(c => c.key === selectedMainCatalog)?.[0]?.linkedFrom?.catalogCollection?.items;
|
||||||
setSubCatalogs(updatedData);
|
const data = updatedData.sort(function (a, b) {
|
||||||
|
if (a.position === null && b.position === null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.position === null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.position === null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.position - b.position;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSubCatalogs(data);
|
||||||
|
|
||||||
//根据主目录过滤app数据
|
//根据主目录过滤app数据
|
||||||
let subCatalogApps = null;
|
let subCatalogApps = null;
|
||||||
|
@ -379,7 +415,7 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
||||||
updatedData =
|
updatedData =
|
||||||
searchString === ""
|
searchString === ""
|
||||||
? apps
|
? apps
|
||||||
: apps.filter(app => { return app.trademark.toLowerCase().includes(searchString) || app.key.toLowerCase().includes(searchString) });
|
: apps.filter(app => { return app.trademark.toLowerCase().includes(searchString) || app.key.toLowerCase().includes(searchString) || app.summary.toLowerCase().includes(searchString) });
|
||||||
|
|
||||||
setAppList(updatedData);
|
setAppList(updatedData);
|
||||||
setIsAllSelected(true);
|
setIsAllSelected(true);
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "./static/css/main.751babb1.css",
|
"main.css": "./static/css/main.1e5ef24c.css",
|
||||||
"main.js": "./static/js/main.495e2b6f.js",
|
"main.js": "./static/js/main.bc6762b5.js",
|
||||||
"static/js/145.2fc71954.chunk.js": "./static/js/145.2fc71954.chunk.js",
|
"static/js/927.1961e223.chunk.js": "./static/js/927.1961e223.chunk.js",
|
||||||
"static/js/376.bc0e5568.chunk.js": "./static/js/376.bc0e5568.chunk.js",
|
"static/js/376.bc0e5568.chunk.js": "./static/js/376.bc0e5568.chunk.js",
|
||||||
"static/js/426.46c5e949.chunk.js": "./static/js/426.46c5e949.chunk.js",
|
"static/js/426.46c5e949.chunk.js": "./static/js/426.46c5e949.chunk.js",
|
||||||
"static/js/603.8e18e7fa.chunk.js": "./static/js/603.8e18e7fa.chunk.js",
|
"static/js/603.8e18e7fa.chunk.js": "./static/js/603.8e18e7fa.chunk.js",
|
||||||
"static/css/836.5576a615.chunk.css": "./static/css/836.5576a615.chunk.css",
|
"static/css/836.5576a615.chunk.css": "./static/css/836.5576a615.chunk.css",
|
||||||
"static/js/836.06772eb6.chunk.js": "./static/js/836.06772eb6.chunk.js",
|
"static/js/836.06772eb6.chunk.js": "./static/js/836.06772eb6.chunk.js",
|
||||||
"static/js/912.1f46d2af.chunk.js": "./static/js/912.1f46d2af.chunk.js",
|
"static/js/912.1f46d2af.chunk.js": "./static/js/912.1f46d2af.chunk.js",
|
||||||
"static/js/96.2a832dc4.chunk.js": "./static/js/96.2a832dc4.chunk.js",
|
"static/js/888.41d89770.chunk.js": "./static/js/888.41d89770.chunk.js",
|
||||||
"static/css/769.c11b83c2.chunk.css": "./static/css/769.c11b83c2.chunk.css",
|
"static/css/769.c11b83c2.chunk.css": "./static/css/769.c11b83c2.chunk.css",
|
||||||
"static/js/769.3b95354d.chunk.js": "./static/js/769.3b95354d.chunk.js",
|
"static/js/769.3b95354d.chunk.js": "./static/js/769.3b95354d.chunk.js",
|
||||||
"static/media/materialdesignicons-webfont.eot": "./static/media/materialdesignicons-webfont.e044ed23c047e571c550.eot",
|
"static/media/materialdesignicons-webfont.eot": "./static/media/materialdesignicons-webfont.e044ed23c047e571c550.eot",
|
||||||
|
@ -63,21 +63,21 @@
|
||||||
"static/media/status-icon-sprite.svg": "./static/media/status-icon-sprite.4fee9fefc3971799d2dd.svg",
|
"static/media/status-icon-sprite.svg": "./static/media/status-icon-sprite.4fee9fefc3971799d2dd.svg",
|
||||||
"static/media/logo-sm.svg": "./static/media/logo-sm.53b8ca70620b0b2968874a3660f195dd.svg",
|
"static/media/logo-sm.svg": "./static/media/logo-sm.53b8ca70620b0b2968874a3660f195dd.svg",
|
||||||
"index.html": "./index.html",
|
"index.html": "./index.html",
|
||||||
"main.751babb1.css.map": "./static/css/main.751babb1.css.map",
|
"main.1e5ef24c.css.map": "./static/css/main.1e5ef24c.css.map",
|
||||||
"main.495e2b6f.js.map": "./static/js/main.495e2b6f.js.map",
|
"main.bc6762b5.js.map": "./static/js/main.bc6762b5.js.map",
|
||||||
"145.2fc71954.chunk.js.map": "./static/js/145.2fc71954.chunk.js.map",
|
"927.1961e223.chunk.js.map": "./static/js/927.1961e223.chunk.js.map",
|
||||||
"376.bc0e5568.chunk.js.map": "./static/js/376.bc0e5568.chunk.js.map",
|
"376.bc0e5568.chunk.js.map": "./static/js/376.bc0e5568.chunk.js.map",
|
||||||
"426.46c5e949.chunk.js.map": "./static/js/426.46c5e949.chunk.js.map",
|
"426.46c5e949.chunk.js.map": "./static/js/426.46c5e949.chunk.js.map",
|
||||||
"603.8e18e7fa.chunk.js.map": "./static/js/603.8e18e7fa.chunk.js.map",
|
"603.8e18e7fa.chunk.js.map": "./static/js/603.8e18e7fa.chunk.js.map",
|
||||||
"836.5576a615.chunk.css.map": "./static/css/836.5576a615.chunk.css.map",
|
"836.5576a615.chunk.css.map": "./static/css/836.5576a615.chunk.css.map",
|
||||||
"836.06772eb6.chunk.js.map": "./static/js/836.06772eb6.chunk.js.map",
|
"836.06772eb6.chunk.js.map": "./static/js/836.06772eb6.chunk.js.map",
|
||||||
"912.1f46d2af.chunk.js.map": "./static/js/912.1f46d2af.chunk.js.map",
|
"912.1f46d2af.chunk.js.map": "./static/js/912.1f46d2af.chunk.js.map",
|
||||||
"96.2a832dc4.chunk.js.map": "./static/js/96.2a832dc4.chunk.js.map",
|
"888.41d89770.chunk.js.map": "./static/js/888.41d89770.chunk.js.map",
|
||||||
"769.c11b83c2.chunk.css.map": "./static/css/769.c11b83c2.chunk.css.map",
|
"769.c11b83c2.chunk.css.map": "./static/css/769.c11b83c2.chunk.css.map",
|
||||||
"769.3b95354d.chunk.js.map": "./static/js/769.3b95354d.chunk.js.map"
|
"769.3b95354d.chunk.js.map": "./static/js/769.3b95354d.chunk.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.751babb1.css",
|
"static/css/main.1e5ef24c.css",
|
||||||
"static/js/main.495e2b6f.js"
|
"static/js/main.bc6762b5.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><script type="text/javascript" src="../base1/cockpit.js"></script><script type="text/javascript" src="../*/po.js"></script><link rel="manifest" href="./manifest.json"/><title>App Store</title><script defer="defer" src="./static/js/main.495e2b6f.js"></script><link href="./static/css/main.751babb1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" style="height:100%"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><script type="text/javascript" src="../base1/cockpit.js"></script><script type="text/javascript" src="../*/po.js"></script><link rel="manifest" href="./manifest.json"/><title>App Store</title><script defer="defer" src="./static/js/main.bc6762b5.js"></script><link href="./static/css/main.1e5ef24c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" style="height:100%"></div></body></html>
|
File diff suppressed because one or more lines are too long
1
cockpit/myapps/build/static/css/main.1e5ef24c.css.map
Normal file
1
cockpit/myapps/build/static/css/main.1e5ef24c.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
cockpit/myapps/build/static/js/888.41d89770.chunk.js
Normal file
3
cockpit/myapps/build/static/js/888.41d89770.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
cockpit/myapps/build/static/js/888.41d89770.chunk.js.map
Normal file
1
cockpit/myapps/build/static/js/888.41d89770.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
cockpit/myapps/build/static/js/927.1961e223.chunk.js
Normal file
2
cockpit/myapps/build/static/js/927.1961e223.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
cockpit/myapps/build/static/js/927.1961e223.chunk.js.map
Normal file
1
cockpit/myapps/build/static/js/927.1961e223.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -108,4 +108,21 @@ File: Main Css File
|
||||||
@import "custom/plugins/react-table"; // react table style
|
@import "custom/plugins/react-table"; // react table style
|
||||||
@import "custom/plugins/ion-rangeslider";
|
@import "custom/plugins/ion-rangeslider";
|
||||||
@import "custom/plugins/jstree";
|
@import "custom/plugins/jstree";
|
||||||
@import "custom/plugins/frappe-gantt";
|
@import "custom/plugins/frappe-gantt";
|
||||||
|
|
||||||
|
|
||||||
|
.myProtainerTerminal {
|
||||||
|
overflow: hidden;
|
||||||
|
/* 16:9 aspect ratio */
|
||||||
|
padding-top: 56.25%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myProtainerTerminal iframe {
|
||||||
|
border: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import axios from 'axios';
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import cockpit from 'cockpit';
|
import cockpit from 'cockpit';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
@ -9,6 +10,7 @@ import { AppRestart, AppStart, AppStop } from '../helpers';
|
||||||
import AppAccess from './appdetailtabs/appaccess';
|
import AppAccess from './appdetailtabs/appaccess';
|
||||||
import AppContainer from './appdetailtabs/appcontainer';
|
import AppContainer from './appdetailtabs/appcontainer';
|
||||||
import AppOverview from './appdetailtabs/appoverview';
|
import AppOverview from './appdetailtabs/appoverview';
|
||||||
|
import AppTerminal from './appdetailtabs/appterminal';
|
||||||
import Uninstall from './appdetailtabs/appuninstall';
|
import Uninstall from './appdetailtabs/appuninstall';
|
||||||
|
|
||||||
const _ = cockpit.gettext;
|
const _ = cockpit.gettext;
|
||||||
|
@ -22,6 +24,105 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
const [restartAppLoading, setRestartAppLoading] = useState(false); //用户显示重启时应用的加载状态
|
const [restartAppLoading, setRestartAppLoading] = useState(false); //用户显示重启时应用的加载状态
|
||||||
const navigate = useNavigate(); //用于页面跳转
|
const navigate = useNavigate(); //用于页面跳转
|
||||||
const childRef = useRef();
|
const childRef = useRef();
|
||||||
|
const [containersInfo, setContainersInfo] = useState([]);
|
||||||
|
const customer_name = props.current_app.customer_name;
|
||||||
|
const [endpointsId, setEndpointsId] = useState(null);
|
||||||
|
const [mainContainerId, setMainContainerId] = useState(null);
|
||||||
|
|
||||||
|
//通过Portainer的接口获取容器数据
|
||||||
|
const getContainersData = async () => {
|
||||||
|
try {
|
||||||
|
let jwt = window.localStorage.getItem("portainer.JWT"); //获取存储在本地的JWT数据
|
||||||
|
let id = null;
|
||||||
|
|
||||||
|
//如果获取不到jwt,则模拟登录并写入新的jwt
|
||||||
|
if (jwt === null) {
|
||||||
|
const response = await axios.get('./config.json'); //从项目下读取配置文件
|
||||||
|
if (response.status === 200) {
|
||||||
|
let config = response.data.PORTAINER;
|
||||||
|
const { PORTAINER_USERNAME, PORTAINER_PASSWORD, PORTAINER_AUTH_URL, PORTAINER_HOME_PAGE } = config;
|
||||||
|
|
||||||
|
//调用portainer的登录API,模拟登录
|
||||||
|
const authResponse = await axios.post(PORTAINER_AUTH_URL, {
|
||||||
|
username: PORTAINER_USERNAME,
|
||||||
|
password: PORTAINER_PASSWORD
|
||||||
|
});
|
||||||
|
if (authResponse.status === 200) {
|
||||||
|
jwt = "\"" + authResponse.data.jwt + "\"";
|
||||||
|
//jwt = authResponse.data.jwt
|
||||||
|
window.localStorage.setItem('portainer\.JWT', jwt); //关键是将通过API登录后获取的jwt,存储到本地localStorage
|
||||||
|
} else {
|
||||||
|
console.error('Error:', authResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Error:', response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//从portainer接口获取endpoints
|
||||||
|
const endpointsData = await axios.get('/portainer/api/endpoints', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (endpointsData.status === 200) {
|
||||||
|
//先判断是否获取了“本地”endpoint
|
||||||
|
if (endpointsData.data.length == 0) { //没有“本地”endpoint
|
||||||
|
//调用添加"本地"环境的接口
|
||||||
|
const addEndpoint = await axios.post('/portainer/api/endpoints', {},
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
Name: "local",
|
||||||
|
EndpointCreationType: 1
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (addEndpoint.status === 200) {
|
||||||
|
id = addEndpoint.data?.Id;
|
||||||
|
setEndpointsId(id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Error:', addEndpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//应该可能会返回“远程”的endpoint,这里只获取“本地”endpoint,条件为URL包含'/var/run/docker.sock'
|
||||||
|
id = endpointsData.data.find(({ URL }) => URL.includes('/var/run/docker.sock')).Id;
|
||||||
|
setEndpointsId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//调用接口获取
|
||||||
|
const containersData = await axios.get(`/portainer/api/endpoints/${id}/docker/containers/json`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
all: true,
|
||||||
|
filters: JSON.stringify({ "label": [`com.docker.compose.project=${customer_name}`] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (containersData.status === 200) {
|
||||||
|
const data = containersData.data;
|
||||||
|
const id = data.find(container => container.Names?.[0]?.replace(/^\/|\/$/g, '') === customer_name)?.Id;
|
||||||
|
setMainContainerId(id);
|
||||||
|
setContainersInfo(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Error:', containersData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Error:', endpointsData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//设置卸载页面的按钮禁用
|
//设置卸载页面的按钮禁用
|
||||||
const setUninstallButtonDisable = () => {
|
const setUninstallButtonDisable = () => {
|
||||||
|
@ -50,6 +151,10 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
setCurrentApp(props.current_app);
|
setCurrentApp(props.current_app);
|
||||||
}, [props.current_app]);
|
}, [props.current_app]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getContainersData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const tabContents = [
|
const tabContents = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
@ -67,10 +172,16 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
id: '3',
|
id: '3',
|
||||||
title: _("Container"),
|
title: _("Container"),
|
||||||
icon: 'mdi dripicons-stack',
|
icon: 'mdi dripicons-stack',
|
||||||
text: <AppContainer data={currentApp} />,
|
text: <AppContainer customer_name={customer_name} endpointsId={endpointsId} containersInfo={containersInfo} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
|
title: _("Terminal"),
|
||||||
|
icon: 'mdi dripicons-stack',
|
||||||
|
text: <AppTerminal endpointsId={endpointsId} containerId={mainContainerId} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
title: _("Uninstall"),
|
title: _("Uninstall"),
|
||||||
icon: 'mdi mdi-cog-outline',
|
icon: 'mdi mdi-cog-outline',
|
||||||
text: <Uninstall data={currentApp} ref={childRef} disabledButton={setAppdetailButtonDisable} enableButton={setAppdetailButtonEnable}
|
text: <Uninstall data={currentApp} ref={childRef} disabledButton={setAppdetailButtonDisable} enableButton={setAppdetailButtonEnable}
|
||||||
|
@ -122,6 +233,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
props.onDataChange();
|
props.onDataChange();
|
||||||
|
getContainersData(); //刷新容器数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@ -166,6 +278,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
props.onDataChange();
|
props.onDataChange();
|
||||||
|
getContainersData(); //刷新容器数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@ -208,6 +321,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
props.onDataChange();
|
props.onDataChange();
|
||||||
|
getContainersData(); //刷新容器数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@ -238,6 +352,11 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
{_("Terminal")}
|
{_("Terminal")}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}>
|
}>
|
||||||
|
{/* <Link to={{ pathname: '/terminal', search: `?id=${currentApp.customer_name}` }}
|
||||||
|
style={{ color: "#fff", backgroundColor: "#727cf5", padding: "5px 10px", borderRadius: "3px", borderColor: "#727cf5", marginRight: "10px" }}
|
||||||
|
target="_blank">
|
||||||
|
<i className="dripicons-code noti-icon"></i>{' '}
|
||||||
|
</Link> */}
|
||||||
<Link to={{ pathname: '/terminal', search: `?id=${currentApp.customer_name}` }}
|
<Link to={{ pathname: '/terminal', search: `?id=${currentApp.customer_name}` }}
|
||||||
style={{ color: "#fff", backgroundColor: "#727cf5", padding: "5px 10px", borderRadius: "3px", borderColor: "#727cf5", marginRight: "10px" }}
|
style={{ color: "#fff", backgroundColor: "#727cf5", padding: "5px 10px", borderRadius: "3px", borderColor: "#727cf5", marginRight: "10px" }}
|
||||||
target="_blank">
|
target="_blank">
|
||||||
|
@ -301,7 +420,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
||||||
return (
|
return (
|
||||||
<Tab.Pane eventKey={tab.title} id={tab.id} key={index} style={{ height: "100%" }}>
|
<Tab.Pane eventKey={tab.title} id={tab.id} key={index} style={{ height: "100%" }}>
|
||||||
<Row style={{ height: "100%" }}>
|
<Row style={{ height: "100%" }}>
|
||||||
<Col sm="12" style={{ height: tab.title === "Terminal" ? "600px" : "" }}>
|
<Col sm="12" /*style={{ height: tab.title === "Terminal" ? "600px" : "" }}*/>
|
||||||
{tab.text}
|
{tab.text}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import axios from 'axios';
|
|
||||||
import cockpit from "cockpit";
|
import cockpit from "cockpit";
|
||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import { Badge, Button, Card, Col, Row, Table } from 'react-bootstrap';
|
import { Badge, Button, Card, Col, Row, Table } from 'react-bootstrap';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -8,106 +7,106 @@ const _ = cockpit.gettext;
|
||||||
|
|
||||||
const AppContainer = (props): React$Element<React$FragmentType> => {
|
const AppContainer = (props): React$Element<React$FragmentType> => {
|
||||||
const navigate = useNavigate(); //用于页面跳转
|
const navigate = useNavigate(); //用于页面跳转
|
||||||
const [containersInfo, setContainersInfo] = useState([]);
|
const containersInfo = props.containersInfo;
|
||||||
const customer_name = props.data.customer_name;
|
const customer_name = props.customer_name;
|
||||||
const [endpointsId, setEndpointsId] = useState(null);
|
const endpointsId = props.endpointsId;
|
||||||
|
|
||||||
//通过Portainer的接口获取容器数据
|
//通过Portainer的接口获取容器数据
|
||||||
const getContainersData = async () => {
|
// const getContainersData = async () => {
|
||||||
try {
|
// try {
|
||||||
let jwt = window.localStorage.getItem("portainer.JWT2"); //获取存储在本地的JWT数据
|
// let jwt = window.localStorage.getItem("portainer.JWT"); //获取存储在本地的JWT数据
|
||||||
let id = null;
|
// let id = null;
|
||||||
|
|
||||||
//如果获取不到jwt,则模拟登录并写入新的jwt
|
// //如果获取不到jwt,则模拟登录并写入新的jwt
|
||||||
if (jwt === null) {
|
// if (jwt === null) {
|
||||||
const response = await axios.get('./config.json'); //从项目下读取配置文件
|
// const response = await axios.get('./config.json'); //从项目下读取配置文件
|
||||||
if (response.status === 200) {
|
// if (response.status === 200) {
|
||||||
let config = response.data.PORTAINER;
|
// let config = response.data.PORTAINER;
|
||||||
const { PORTAINER_USERNAME, PORTAINER_PASSWORD, PORTAINER_AUTH_URL, PORTAINER_HOME_PAGE } = config;
|
// const { PORTAINER_USERNAME, PORTAINER_PASSWORD, PORTAINER_AUTH_URL, PORTAINER_HOME_PAGE } = config;
|
||||||
|
|
||||||
//调用portainer的登录API,模拟登录
|
// //调用portainer的登录API,模拟登录
|
||||||
const authResponse = await axios.post(PORTAINER_AUTH_URL, {
|
// const authResponse = await axios.post(PORTAINER_AUTH_URL, {
|
||||||
username: PORTAINER_USERNAME,
|
// username: PORTAINER_USERNAME,
|
||||||
password: PORTAINER_PASSWORD
|
// password: PORTAINER_PASSWORD
|
||||||
});
|
// });
|
||||||
if (authResponse.status === 200) {
|
// if (authResponse.status === 200) {
|
||||||
jwt = "\"" + authResponse.data.jwt + "\"";
|
// jwt = "\"" + authResponse.data.jwt + "\"";
|
||||||
//jwt = authResponse.data.jwt
|
// //jwt = authResponse.data.jwt
|
||||||
window.localStorage.setItem('portainer\.JWT2', jwt); //关键是将通过API登录后获取的jwt,存储到本地localStorage
|
// window.localStorage.setItem('portainer\.JWT', jwt); //关键是将通过API登录后获取的jwt,存储到本地localStorage
|
||||||
} else {
|
// } else {
|
||||||
console.error('Error:', authResponse);
|
// console.error('Error:', authResponse);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
console.error('Error:', response);
|
// console.error('Error:', response);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
//从portainer接口获取endpoints
|
// //从portainer接口获取endpoints
|
||||||
const endpointsData = await axios.get('/portainer/api/endpoints', {
|
// const endpointsData = await axios.get('/portainer/api/endpoints', {
|
||||||
headers: {
|
// headers: {
|
||||||
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
// 'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
if (endpointsData.status === 200) {
|
// if (endpointsData.status === 200) {
|
||||||
//先判断是否获取了“本地”endpoint
|
// //先判断是否获取了“本地”endpoint
|
||||||
if (endpointsData.data.length == 0) { //没有“本地”endpoint
|
// if (endpointsData.data.length == 0) { //没有“本地”endpoint
|
||||||
//调用添加"本地"环境的接口
|
// //调用添加"本地"环境的接口
|
||||||
const addEndpoint = await axios.post('/portainer/api/endpoints', {},
|
// const addEndpoint = await axios.post('/portainer/api/endpoints', {},
|
||||||
{
|
// {
|
||||||
params: {
|
// params: {
|
||||||
Name: "websoft9-local",
|
// Name: "websoft9-local",
|
||||||
EndpointCreationType: 1
|
// EndpointCreationType: 1
|
||||||
},
|
// },
|
||||||
headers: {
|
// headers: {
|
||||||
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
// 'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
if (addEndpoint.status === 200) {
|
// if (addEndpoint.status === 200) {
|
||||||
id = addEndpoint.data?.Id;
|
// id = addEndpoint.data?.Id;
|
||||||
setEndpointsId(id);
|
// setEndpointsId(id);
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
console.error('Error:', addEndpoint);
|
// console.error('Error:', addEndpoint);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
//应该可能会返回“远程”的endpoint,这里只获取“本地”endpoint,条件为URL包含'/var/run/docker.sock'
|
// //应该可能会返回“远程”的endpoint,这里只获取“本地”endpoint,条件为URL包含'/var/run/docker.sock'
|
||||||
id = endpointsData.data.find(({ URL }) => URL.includes('/var/run/docker.sock')).Id;
|
// id = endpointsData.data.find(({ URL }) => URL.includes('/var/run/docker.sock')).Id;
|
||||||
setEndpointsId(id);
|
// setEndpointsId(id);
|
||||||
}
|
// }
|
||||||
|
|
||||||
//调用接口获取
|
// //调用接口获取
|
||||||
const containersData = await axios.get(`/portainer/api/endpoints/${id}/docker/containers/json`, {
|
// const containersData = await axios.get(`/portainer/api/endpoints/${id}/docker/containers/json`, {
|
||||||
headers: {
|
// headers: {
|
||||||
'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
// 'Authorization': 'Bearer ' + jwt.replace(/"/g, '')
|
||||||
},
|
// },
|
||||||
params: {
|
// params: {
|
||||||
all: true,
|
// all: true,
|
||||||
filters: JSON.stringify({ "label": [`com.docker.compose.project=${customer_name}`] })
|
// filters: JSON.stringify({ "label": [`com.docker.compose.project=${customer_name}`] })
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
if (containersData.status === 200) {
|
// if (containersData.status === 200) {
|
||||||
setContainersInfo(containersData.data);
|
// setContainersInfo(containersData.data);
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
console.error('Error:', containersData);
|
// console.error('Error:', containersData);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
console.error('Error:', endpointsData);
|
// console.error('Error:', endpointsData);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
catch (error) {
|
// catch (error) {
|
||||||
console.error('Error:', error);
|
// console.error('Error:', error);
|
||||||
//navigate("/error-500");
|
// //navigate("/error-500");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
getContainersData();
|
// getContainersData();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
|
|
94
cockpit/myapps/src/pages/appdetailtabs/appterminal.js
Normal file
94
cockpit/myapps/src/pages/appdetailtabs/appterminal.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import Spinner from 'react-bootstrap/Spinner';
|
||||||
|
|
||||||
|
// 把handleIframe函数提取成一个自定义hook,并使用useCallback来缓存它
|
||||||
|
const useHandleIframe = () => {
|
||||||
|
return useCallback((iframe) => {
|
||||||
|
var iframeWindow = iframe.contentWindow;
|
||||||
|
|
||||||
|
var pageWrapper = iframeWindow.document.getElementById("page-wrapper");
|
||||||
|
if (pageWrapper) {
|
||||||
|
var sideview = pageWrapper.querySelector("#sideview");
|
||||||
|
if (sideview) {
|
||||||
|
pageWrapper.removeChild(sideview);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageHeaders = pageWrapper.querySelectorAll("page-header");
|
||||||
|
for (var i = 0; i < pageHeaders.length; i++) {
|
||||||
|
var pageHeader = pageHeaders[i];
|
||||||
|
var parent = pageHeader.parentNode;
|
||||||
|
parent.removeChild(pageHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rdWidgetHeaders = pageWrapper.querySelectorAll("rd-widget-header");
|
||||||
|
for (var i = 0; i < rdWidgetHeaders.length; i++) {
|
||||||
|
var rdWidgetHeade = rdWidgetHeaders[i];
|
||||||
|
var parent = rdWidgetHeade.parentNode;
|
||||||
|
parent.removeChild(rdWidgetHeade);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageWrapper.style.setProperty("padding-left", "0px");
|
||||||
|
pageWrapper.removeAttribute("ng-class");
|
||||||
|
pageWrapper.className = "";
|
||||||
|
|
||||||
|
iframe.style.display = "block";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppTerminal = (props): React$Element<React$FragmentType> => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const endpointsId = props.endpointsId;
|
||||||
|
const containerId = props.containerId;
|
||||||
|
|
||||||
|
// 使用自定义hook来获取handleIframe函数
|
||||||
|
const handleIframe = useHandleIframe();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (iframeRef.current) {
|
||||||
|
// 创建一个MutationObserver来监听iframe中的内容的变化
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
// 当变化发生时,执行handleIframe函数
|
||||||
|
handleIframe(iframeRef.current);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
// 设置观察选项,观察子节点和属性的变化
|
||||||
|
const config = { childList: true, attributes: true };
|
||||||
|
// 判断iframe的contentDocument是否不为null
|
||||||
|
if (iframeRef.current.contentDocument) {
|
||||||
|
// 开始观察iframe中的文档根节点
|
||||||
|
observer.observe(iframeRef.current.contentDocument.documentElement, config);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
// 停止观察
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [iframeRef.current, handleIframe]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
loading && (
|
||||||
|
<div className="d-flex align-items-center justify-content-center m-5">
|
||||||
|
<Spinner animation="border" variant="secondary" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div class="myProtainerTerminal" key="myProtainerTerminal" >
|
||||||
|
<iframe
|
||||||
|
id="myIframe"
|
||||||
|
title="myProtainerTerminal"
|
||||||
|
src={`/portainer/#!/${endpointsId}/docker/containers/${containerId}/exec`}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
ref={iframeRef}
|
||||||
|
sandbox='allow-scripts allow-modal allow-same-origin'
|
||||||
|
loading='eager'
|
||||||
|
/>
|
||||||
|
</div >
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppTerminal;
|
|
@ -228,8 +228,10 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
||||||
|
|
||||||
//用于用户点击应用详情
|
//用于用户点击应用详情
|
||||||
const handleClick = (app) => {
|
const handleClick = (app) => {
|
||||||
setSelectedApp(app);
|
if (app.status === "running" || app.status === "exited") {
|
||||||
setShowModal(true);
|
setSelectedApp(app);
|
||||||
|
setShowModal(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//用于应用为failed时删除应用
|
//用于应用为failed时删除应用
|
||||||
|
@ -308,7 +310,7 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
||||||
else {
|
else {
|
||||||
setShowAlert(true);
|
setShowAlert(true);
|
||||||
setAlertType("success")
|
setAlertType("success")
|
||||||
setAlertMessage("执行成功");
|
setAlertMessage(_("Success"));
|
||||||
handleDataChange();
|
handleDataChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,7 +349,7 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
||||||
<FormInput
|
<FormInput
|
||||||
type="text"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
placeholder={_("Search for apps like WordPress, Dropbox, Slack, Trello, …")}
|
placeholder={_("Search for apps like WordPress, MySQL, GitLab, …")}
|
||||||
onChange={(e) => handleInputChange(e.target.value)}
|
onChange={(e) => handleInputChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -377,7 +379,7 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
||||||
<h4 style={official_app ? {} : { paddingTop: "10px" }}>{official_app ? _("Websoft9's Apps") : _("Other Apps")}</h4>
|
<h4 style={official_app ? {} : { paddingTop: "10px" }}>{official_app ? _("Websoft9's Apps") : _("Other Apps")}</h4>
|
||||||
{filteredApps.map((app, i) => (
|
{filteredApps.map((app, i) => (
|
||||||
<Col xxl={2} md={3} key={app.app_id + i} className="appstore-item">
|
<Col xxl={2} md={3} key={app.app_id + i} className="appstore-item">
|
||||||
<div className='appstore-item-content highlight text-align-center'>
|
<div className='appstore-item-content highlight text-align-center' onClick={() => { handleClick(app) }}>
|
||||||
{
|
{
|
||||||
(!official_app && (app.status === "running" || app.status === "exited")) &&
|
(!official_app && (app.status === "running" || app.status === "exited")) &&
|
||||||
<Dropdown className="float-end">
|
<Dropdown className="float-end">
|
||||||
|
|
Loading…
Reference in a new issue