index.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Card Waterfall</title>
  7. <link rel="stylesheet" href="./static/styles.css">
  8. <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/3.2.31/vue.global.min.js"></script>
  9. <style>
  10. </style>
  11. </head>
  12. <body>
  13. <div id="app">
  14. <div class="card-container">
  15. <div v-for="(card, index) in cards" :key="index" class="card" @click="showCardDetails(card)">
  16. <img :src="card.image" alt="Card Image">
  17. <div class="title">{{ card.title }}</div>
  18. <div class="content">{{ card.content }}</div>
  19. <div class="date">{{ card.date }}</div>
  20. </div>
  21. <!-- Default Add Card -->
  22. <div class="card add-card" @click="openAddCardModal">
  23. <div class="add-icon">+</div>
  24. </div>
  25. </div>
  26. <!--card Modal -->
  27. <div class="card-modal" :class="{ active: showModal }">
  28. <div class="card-modal-container">
  29. <div class="card-modal-left">
  30. <button class="card-modal-close-button" @click="showModal = false">关闭</button>
  31. </div>
  32. <div class="card-modal-right">
  33. <h3>{{ modalTitle }}</h3>
  34. <div v-if="modalCard">
  35. <div v-for="(group, groupIndex) in modalCard.groups" :key="groupIndex" class="card-modal-group-box-container">
  36. <!-- 循环展示 conn_info 里的信息 -->
  37. <div v-for="(conn, connIndex) in group.conn_info" :key="connIndex" class="card-modal-group-box">
  38. <div class="state-box">节点状态</div>
  39. <div class="title"> <strong>节点 {{ connIndex + 1 }}</strong></div>
  40. <div class="content">{{ conn.ssh_info.host }} <strong>端口:</strong> {{ conn.ssh_info.port }}</div>
  41. <p><strong>用户:</strong> {{ conn.ssh_info.username }} <strong>密码:</strong> {{ conn.ssh_info.password }}</p>
  42. <p><strong>数据库:</strong> {{ conn.db_info.database }} <strong></strong>端口:</strong> {{ conn.db_info.port }}</p>
  43. <p><strong>用户:</strong> {{ conn.db_info.user }} <strong>密码:</strong> {{ conn.db_info.password }}</p>
  44. <div class="menu-bar">
  45. <button @click="modifyGroup(connIndex)">修改</button>
  46. <button @click="groupNodeDetail(conn,group.base_filename)">详情</button>
  47. <button @click="setSystemInfo(conn,group.base_filename)">获取系统信息</button>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. <div v-else>
  53. <div v-for="(group, index) in formGroups" :key="index" class="group-box">
  54. <input v-model="group.ssh_ip" placeholder="服务器 IP 地址(必选项)">
  55. <input v-model="group.ssh_port" placeholder="服务器 SSH 端口 默认 22">
  56. <input v-model="group.ssh_username" placeholder="服务器 登录用户名">
  57. <input v-model="group.ssh_password" placeholder="服务器 登录密码">
  58. <input v-model="group.db_port" placeholder="数据库 端口">
  59. <input v-model="group.db_database" placeholder="数据库 库名">
  60. <input v-model="group.db_user" placeholder="数据库 用户名">
  61. <input v-model="group.db_password" placeholder="数据库 密码">
  62. </div>
  63. <div class="modal-buttons">
  64. <button @click="addGroup">新增一组</button>
  65. <button @click="removeGroup" v-if="formGroups.length > 1">删除一组</button>
  66. </div>
  67. <div class="modal-buttons">
  68. <button @click="addNewCard">添加</button>
  69. <button @click="showModal = false">取消</button>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- Group Node Detail Modal 组里节点的模态框-->
  76. <div class="group-node-modal" :class="{ active: showGroupModal }">
  77. <div class="group-node-modal-container">
  78. <div class="group-node-modal-content">
  79. <button class="group-node-modal-close-button" @click="showGroupModal = false">关闭</button>
  80. <h3>组详细信息</h3>
  81. <div v-if="currentGroup && currentGroup.systemInfo">
  82. <p><strong>节点状态:</strong> <span>{{ currentGroup.systemInfo.status }}</span></p>
  83. <p><strong>SSH 信息:</strong></p>
  84. <p><strong>Host:</strong> <span>{{ currentGroup.systemInfo.host }}</span></p>
  85. <p><strong>Port:</strong> <span>{{ currentGroup.systemInfo.port }}</span></p>
  86. <p><strong>Username:</strong> <span>{{ currentGroup.systemInfo.username }}</span></p>
  87. <p><strong>Password:</strong> <span>{{ currentGroup.systemInfo.password }}</span></p>
  88. <p><strong>系统信息:</strong></p>
  89. <p><strong>操作系统 (OS):</strong> <span>{{ currentGroup.systemInfo.os || '再刷新' }}</span></p>
  90. <p><strong>CPU:</strong> <span>{{ currentGroup.systemInfo.cpu || '再刷新' }}</span></p>
  91. <p><strong>内存 (Memory):</strong> <span>{{ currentGroup.systemInfo.memory || '再刷新' }}</span></p>
  92. <p><strong>磁盘 (Disk):</strong> <span>{{ currentGroup.systemInfo.disk || '再刷新' }}</span></p>
  93. <p><strong>磁盘速度 (DiskSpeed):</strong> <span>{{ currentGroup.systemInfo.diskSpeed || '再刷新' }}</span></p>
  94. <p><strong>网络 (Network):</strong> <span>{{ currentGroup.systemInfo.network || '再刷新' }}</span></p>
  95. <p><strong>网络速度 (NetworkSpeed):</strong> <span>{{ currentGroup.systemInfo.networkSpeed || '再刷新' }}</span></p>
  96. <p><strong>错误 (Error):</strong> <span>{{ currentGroup.systemInfo.error || '再刷新' }}</span></p>
  97. <!-- 添加 "获取最新系统信息" 按钮 -->
  98. <button @click="setSystemInfo(currentGroup, group.base_filename)">获取最新系统信息</button>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. <script>
  105. const app = Vue.createApp({
  106. data() {
  107. return {
  108. cards: [],
  109. showModal: false,
  110. formGroups: [
  111. {
  112. db_database: '',
  113. db_password: '',
  114. db_port: '',
  115. db_user: '',
  116. ssh_ip: '',
  117. ssh_password: '',
  118. ssh_port: '',
  119. ssh_username: ''
  120. }
  121. ],
  122. modalCard: null,
  123. modalTitle: '',
  124. showGroupModal: false,
  125. currentGroup: null
  126. };
  127. },
  128. mounted() {
  129. this.fetchCardData();
  130. },
  131. methods: {
  132. openAddCardModal() {
  133. this.modalCard = null;
  134. this.modalTitle = '添加新节点';
  135. this.showModal = true;
  136. },
  137. addGroup() {
  138. const lastGroup = this.formGroups[this.formGroups.length - 1];
  139. this.formGroups.push(Object.assign({}, lastGroup));
  140. },
  141. removeGroup() {
  142. if (this.formGroups.length > 1) {
  143. this.formGroups.pop();
  144. }
  145. },
  146. addNewCard() {
  147. console.log('开始添加新卡片,准备发送数据...');
  148. const groupsContent = this.formGroups.map(group => `数据库: ${group.db_database}, 用户: ${group.db_user}, 端口: ${group.db_port}`).join('; ');
  149. const newCardData = {
  150. image: 'https://via.placeholder.com/300',
  151. title: '数据库连接信息',
  152. content: groupsContent,
  153. date: new Date().toISOString().split('T')[0]
  154. };
  155. // 添加卡片到UI
  156. this.cards.push(newCardData);
  157. // 准备发送到服务器的数据
  158. const dataToSend = this.formGroups.map(group => ({
  159. ssh_info: {
  160. username: group.ssh_username,
  161. password: group.ssh_password,
  162. host: group.ssh_ip,
  163. port: group.ssh_port
  164. },
  165. db_info: {
  166. user: group.db_user,
  167. password: group.db_password,
  168. database: group.db_database,
  169. port: group.db_port
  170. }
  171. }));
  172. // 发送数据到服务器
  173. console.log('正在发送数据:', JSON.stringify(dataToSend));
  174. fetch('http://127.0.0.1:8080/api/AddConnectInfo', {
  175. method: 'POST',
  176. headers: {
  177. 'Content-Type': 'application/json'
  178. },
  179. body: JSON.stringify(dataToSend)
  180. })
  181. .then(response => {
  182. if (!response.ok) {
  183. throw new Error('网络响应不正常');
  184. }
  185. return response.json();
  186. })
  187. .then(data => {
  188. console.log('数据发送成功:', data);
  189. })
  190. .catch(error => {
  191. console.error('发送数据时出错:', error);
  192. });
  193. // 关闭模态框并重置表单
  194. this.showModal = false;
  195. this.resetFormData();
  196. },
  197. resetFormData() {
  198. this.formGroups = [
  199. {
  200. db_database: '',
  201. db_password: '',
  202. db_port: '',
  203. db_user: '',
  204. ssh_ip: '',
  205. ssh_password: '',
  206. ssh_port: '',
  207. ssh_username: ''
  208. }
  209. ];
  210. },
  211. fetchCardData() {
  212. fetch('http://127.0.0.1:8080/api/GetConnectInfo')
  213. .then(response => response.json())
  214. .then(data => {
  215. const connectInfo = data.data;
  216. console.log("connectInfo :", connectInfo);
  217. for (let host in connectInfo) {
  218. if (Object.keys(connectInfo[host]).length === 0) continue;
  219. const groupData = connectInfo[host];
  220. const groups = [];
  221. for (let group in groupData) {
  222. groups.push(groupData[group]);
  223. }
  224. const cardData = {
  225. image: 'https://via.placeholder.com/300',
  226. title: `连接信息 - ${host}`,
  227. groups: groups,
  228. content: `共 ${groups.length} 组连接信息`,
  229. date: new Date().toISOString().split('T')[0]
  230. };
  231. this.cards.push(cardData);
  232. }
  233. })
  234. .catch(error => {
  235. console.error('Error fetching connect info:', error);
  236. });
  237. },
  238. showCardDetails(card) {
  239. console.log("showCardDetails(card) :", card);
  240. this.modalCard = card;
  241. this.modalTitle = card.title;
  242. this.showModal = true;
  243. },
  244. modifyGroup(index) {
  245. alert(`修改组 ${index + 1} 的信息`);
  246. },
  247. groupNodeDetail(groups, base_filename) {
  248. console.log("groupNodeDetail(groups) :", groups);
  249. this.currentGroup = groups; // 设置当前组为选中的组
  250. this.showGroupModal = true; // 显示组详情模态框
  251. // 构建要发送的数据
  252. const dataToSend = {
  253. base_filename: base_filename || "default_filename", // 如果没有提供 base_filename,可以使用默认值
  254. username: groups.ssh_info.username,
  255. password: groups.ssh_info.password,
  256. host: groups.ssh_info.host,
  257. port: groups.ssh_info.port
  258. };
  259. console.log('发送的数据:', JSON.stringify(dataToSend));
  260. // 发送 POST 请求到后端接口
  261. fetch('http://127.0.0.1:8080/api/GetSingleSystemInfo', {
  262. method: 'POST',
  263. headers: {
  264. 'Content-Type': 'application/json'
  265. },
  266. body: JSON.stringify(dataToSend)
  267. })
  268. .then(response => {
  269. if (!response.ok) {
  270. throw new Error('网络响应不正常');
  271. }
  272. return response.json();
  273. })
  274. .then(data => {
  275. console.log('接收到的系统信息:', data);
  276. // 将返回的数据保存到 currentGroup 对象中
  277. this.currentGroup.systemInfo = {
  278. os: data.data.Os,
  279. cpu: data.data.Cpu,
  280. memory: data.data.Memory,
  281. disk: data.data.Disk,
  282. diskSpeed: data.data.DiskSpeed,
  283. network: data.data.Network,
  284. networkSpeed: data.data.NetworkSpeed,
  285. error: data.data.Error
  286. };
  287. })
  288. .catch(error => {
  289. console.error('获取系统信息时出错:', error);
  290. });
  291. },
  292. setSystemInfo(groups, base_filename) {
  293. console.log('正在获取系统信息,groups:', groups);
  294. const connection = groups;
  295. const dataToSend = [{
  296. base_filename: base_filename,
  297. username: connection.ssh_info.username,
  298. password: connection.ssh_info.password,
  299. host: connection.ssh_info.host,
  300. port: connection.ssh_info.port
  301. }];
  302. console.log('正在获取系统信息,发送的数据:', JSON.stringify(dataToSend));
  303. // 1. 发送 POST 请求启动任务
  304. fetch('http://127.0.0.1:8080/api/SetSystemInfo', {
  305. method: 'POST',
  306. headers: {
  307. 'Content-Type': 'application/json'
  308. },
  309. body: JSON.stringify(dataToSend)
  310. })
  311. .then(response => {
  312. console.log('/api/SetSystemInfo POST发送的数据:', JSON.stringify(dataToSend));
  313. if (!response.ok) {
  314. throw new Error('网络响应不正常');
  315. }
  316. return response.json(); // 假设返回 JSON 格式数据,其中包含 progressID
  317. })
  318. .then(data => {
  319. const progressID = data.progressID;
  320. console.log('收到的 progressID:', progressID, data);
  321. // 2. 建立 SSE 长连接,发送 progressID 并接收进度更新
  322. const eventSource = new EventSource(`http://127.0.0.1:8080/api/GetSystemInfoProgress?progressID=${progressID}`);
  323. eventSource.addEventListener('progress', (event) => {
  324. const progressData = JSON.parse(event.data);
  325. console.log("进度更新:", progressData.message);
  326. // 获取字段名称 (name) 和数据 (output)
  327. const [name, server, output] = progressData.message.split(": ");
  328. // 更新 currentGroup.systemInfo 的相应字段
  329. if (this.currentGroup.systemInfo.hasOwnProperty(name)) {
  330. this.$set(this.currentGroup.systemInfo, name, output || '再刷新');
  331. }
  332. });
  333. eventSource.onerror = (event) => {
  334. console.error("EventSource failed:", event);
  335. eventSource.close(); // 出错时关闭连接
  336. };
  337. })
  338. .catch(error => {
  339. console.error('获取系统信息时出错:', error);
  340. });
  341. }
  342. }
  343. });
  344. app.mount('#app');
  345. </script>
  346. </body>
  347. </html>