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": {
|
||||
"main.css": "./static/css/main.751babb1.css",
|
||||
"main.js": "./static/js/main.a27efeeb.js",
|
||||
"static/js/688.bf21350d.chunk.js": "./static/js/688.bf21350d.chunk.js",
|
||||
"main.js": "./static/js/main.8d3e5d4b.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/426.910887ac.chunk.js": "./static/js/426.910887ac.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",
|
||||
"index.html": "./index.html",
|
||||
"main.751babb1.css.map": "./static/css/main.751babb1.css.map",
|
||||
"main.a27efeeb.js.map": "./static/js/main.a27efeeb.js.map",
|
||||
"688.bf21350d.chunk.js.map": "./static/js/688.bf21350d.chunk.js.map",
|
||||
"main.8d3e5d4b.js.map": "./static/js/main.8d3e5d4b.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",
|
||||
"426.910887ac.chunk.js.map": "./static/js/426.910887ac.chunk.js.map",
|
||||
"912.833f32c9.chunk.js.map": "./static/js/912.833f32c9.chunk.js.map",
|
||||
|
@ -53,6 +53,6 @@
|
|||
},
|
||||
"entrypoints": [
|
||||
"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
|
||||
}
|
||||
key
|
||||
hot
|
||||
trademark
|
||||
summary
|
||||
overview
|
||||
|
@ -39,11 +40,11 @@ const getContentfulData = gql`
|
|||
logo {
|
||||
imageurl
|
||||
}
|
||||
catalogCollection(limit:20) {
|
||||
catalogCollection(limit:15) {
|
||||
items {
|
||||
key
|
||||
title
|
||||
catalogCollection(limit:1){
|
||||
catalogCollection(limit:5){
|
||||
items{
|
||||
key
|
||||
title
|
||||
|
@ -58,12 +59,14 @@ const getContentfulData = gql`
|
|||
catalogCollection(limit:20) {
|
||||
items {
|
||||
key
|
||||
position
|
||||
title
|
||||
linkedFrom(allowedLocales:["en-US"]) {
|
||||
catalogCollection(limit:20) {
|
||||
items {
|
||||
key
|
||||
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 [appList, setAppList] = useState(null); //用于存储通过目录筛选出来的数据
|
||||
|
@ -314,8 +327,20 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
|||
skipCount += allData.productCollection.items.length;
|
||||
// 调用fetchMoreProducts函数来获取更多的产品,如果有的话
|
||||
fetchMoreProducts();
|
||||
setAppList(allData.productCollection?.items);
|
||||
setApps(allData.productCollection?.items);
|
||||
//对产品根据hot排序:降序
|
||||
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])
|
||||
|
||||
|
@ -345,7 +370,18 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
|||
selectedMainCatalog === 'All'
|
||||
? []
|
||||
: 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数据
|
||||
let subCatalogApps = null;
|
||||
|
@ -379,7 +415,7 @@ const AppStore = (): React$Element<React$FragmentType> => {
|
|||
updatedData =
|
||||
searchString === ""
|
||||
? 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);
|
||||
setIsAllSelected(true);
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.751babb1.css",
|
||||
"main.js": "./static/js/main.495e2b6f.js",
|
||||
"static/js/145.2fc71954.chunk.js": "./static/js/145.2fc71954.chunk.js",
|
||||
"main.css": "./static/css/main.1e5ef24c.css",
|
||||
"main.js": "./static/js/main.bc6762b5.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/426.46c5e949.chunk.js": "./static/js/426.46c5e949.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/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/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/js/769.3b95354d.chunk.js": "./static/js/769.3b95354d.chunk.js",
|
||||
"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/logo-sm.svg": "./static/media/logo-sm.53b8ca70620b0b2968874a3660f195dd.svg",
|
||||
"index.html": "./index.html",
|
||||
"main.751babb1.css.map": "./static/css/main.751babb1.css.map",
|
||||
"main.495e2b6f.js.map": "./static/js/main.495e2b6f.js.map",
|
||||
"145.2fc71954.chunk.js.map": "./static/js/145.2fc71954.chunk.js.map",
|
||||
"main.1e5ef24c.css.map": "./static/css/main.1e5ef24c.css.map",
|
||||
"main.bc6762b5.js.map": "./static/js/main.bc6762b5.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",
|
||||
"426.46c5e949.chunk.js.map": "./static/js/426.46c5e949.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.06772eb6.chunk.js.map": "./static/js/836.06772eb6.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.3b95354d.chunk.js.map": "./static/js/769.3b95354d.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.751babb1.css",
|
||||
"static/js/main.495e2b6f.js"
|
||||
"static/css/main.1e5ef24c.css",
|
||||
"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/ion-rangeslider";
|
||||
@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 cockpit from 'cockpit';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
@ -9,6 +10,7 @@ import { AppRestart, AppStart, AppStop } from '../helpers';
|
|||
import AppAccess from './appdetailtabs/appaccess';
|
||||
import AppContainer from './appdetailtabs/appcontainer';
|
||||
import AppOverview from './appdetailtabs/appoverview';
|
||||
import AppTerminal from './appdetailtabs/appterminal';
|
||||
import Uninstall from './appdetailtabs/appuninstall';
|
||||
|
||||
const _ = cockpit.gettext;
|
||||
|
@ -22,6 +24,105 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
const [restartAppLoading, setRestartAppLoading] = useState(false); //用户显示重启时应用的加载状态
|
||||
const navigate = useNavigate(); //用于页面跳转
|
||||
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 = () => {
|
||||
|
@ -50,6 +151,10 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
setCurrentApp(props.current_app);
|
||||
}, [props.current_app]);
|
||||
|
||||
useEffect(() => {
|
||||
getContainersData();
|
||||
}, []);
|
||||
|
||||
const tabContents = [
|
||||
{
|
||||
id: '1',
|
||||
|
@ -67,10 +172,16 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
id: '3',
|
||||
title: _("Container"),
|
||||
icon: 'mdi dripicons-stack',
|
||||
text: <AppContainer data={currentApp} />,
|
||||
text: <AppContainer customer_name={customer_name} endpointsId={endpointsId} containersInfo={containersInfo} />,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: _("Terminal"),
|
||||
icon: 'mdi dripicons-stack',
|
||||
text: <AppTerminal endpointsId={endpointsId} containerId={mainContainerId} />
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: _("Uninstall"),
|
||||
icon: 'mdi mdi-cog-outline',
|
||||
text: <Uninstall data={currentApp} ref={childRef} disabledButton={setAppdetailButtonDisable} enableButton={setAppdetailButtonEnable}
|
||||
|
@ -122,6 +233,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
}
|
||||
else {
|
||||
props.onDataChange();
|
||||
getContainersData(); //刷新容器数据
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -166,6 +278,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
}
|
||||
else {
|
||||
props.onDataChange();
|
||||
getContainersData(); //刷新容器数据
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -208,6 +321,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
}
|
||||
else {
|
||||
props.onDataChange();
|
||||
getContainersData(); //刷新容器数据
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -238,6 +352,11 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
{_("Terminal")}
|
||||
</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}` }}
|
||||
style={{ color: "#fff", backgroundColor: "#727cf5", padding: "5px 10px", borderRadius: "3px", borderColor: "#727cf5", marginRight: "10px" }}
|
||||
target="_blank">
|
||||
|
@ -301,7 +420,7 @@ const AppDetailModal = (props): React$Element<React$FragmentType> => {
|
|||
return (
|
||||
<Tab.Pane eventKey={tab.title} id={tab.id} key={index} 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}
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import axios from 'axios';
|
||||
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 { useNavigate } from 'react-router-dom';
|
||||
|
||||
|
@ -8,106 +7,106 @@ const _ = cockpit.gettext;
|
|||
|
||||
const AppContainer = (props): React$Element<React$FragmentType> => {
|
||||
const navigate = useNavigate(); //用于页面跳转
|
||||
const [containersInfo, setContainersInfo] = useState([]);
|
||||
const customer_name = props.data.customer_name;
|
||||
const [endpointsId, setEndpointsId] = useState(null);
|
||||
const containersInfo = props.containersInfo;
|
||||
const customer_name = props.customer_name;
|
||||
const endpointsId = props.endpointsId;
|
||||
|
||||
//通过Portainer的接口获取容器数据
|
||||
const getContainersData = async () => {
|
||||
try {
|
||||
let jwt = window.localStorage.getItem("portainer.JWT2"); //获取存储在本地的JWT数据
|
||||
let id = null;
|
||||
// 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;
|
||||
// //如果获取不到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\.JWT2', jwt); //关键是将通过API登录后获取的jwt,存储到本地localStorage
|
||||
} else {
|
||||
console.error('Error:', authResponse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('Error:', response);
|
||||
}
|
||||
}
|
||||
// //调用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: "websoft9-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);
|
||||
}
|
||||
// //从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: "websoft9-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) {
|
||||
setContainersInfo(containersData.data);
|
||||
}
|
||||
else {
|
||||
console.error('Error:', containersData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('Error:', endpointsData);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error:', error);
|
||||
//navigate("/error-500");
|
||||
}
|
||||
}
|
||||
// //调用接口获取
|
||||
// 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) {
|
||||
// setContainersInfo(containersData.data);
|
||||
// }
|
||||
// else {
|
||||
// console.error('Error:', containersData);
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// console.error('Error:', endpointsData);
|
||||
// }
|
||||
// }
|
||||
// catch (error) {
|
||||
// console.error('Error:', error);
|
||||
// //navigate("/error-500");
|
||||
// }
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
getContainersData();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// getContainersData();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<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) => {
|
||||
setSelectedApp(app);
|
||||
setShowModal(true);
|
||||
if (app.status === "running" || app.status === "exited") {
|
||||
setSelectedApp(app);
|
||||
setShowModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
//用于应用为failed时删除应用
|
||||
|
@ -308,7 +310,7 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
|||
else {
|
||||
setShowAlert(true);
|
||||
setAlertType("success")
|
||||
setAlertMessage("执行成功");
|
||||
setAlertMessage(_("Success"));
|
||||
handleDataChange();
|
||||
}
|
||||
}
|
||||
|
@ -347,7 +349,7 @@ const MyApps = (): React$Element<React$FragmentType> => {
|
|||
<FormInput
|
||||
type="text"
|
||||
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)}
|
||||
/>
|
||||
</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>
|
||||
{filteredApps.map((app, i) => (
|
||||
<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")) &&
|
||||
<Dropdown className="float-end">
|
||||
|
|
Loading…
Reference in a new issue