Greasy Fork镜像 支持简体中文。

rancher log collect [rancher日志收集]

用于收集rancher中pod的日志

  1. // ==UserScript==
  2. // @name rancher log collect [rancher日志收集]
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.1
  5. // @description 用于收集rancher中pod的日志
  6. // @author maple.
  7. // @match https://rancher.indata.cc/dashboard/*
  8. // @icon https://www.rancher.com/assets/img/favicon.png
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // 需要hook的ws包含url
  17. const TARGET_URL = 'exec';
  18. const originalWebSocket = window.WebSocket;
  19.  
  20. let wsIdIncr = 0;
  21.  
  22. /**
  23. * rancher exec ws中,发送消息前缀是0; 接受消息前缀是1
  24. */
  25. class WebSocketProxy extends originalWebSocket {
  26.  
  27. wsId = 0;
  28. url = '';
  29.  
  30. constructor(...args) {
  31. super(...args);
  32.  
  33. // 检查是否需要hook这个WebSocket连接
  34. if (!this.shouldHook(args[0])) {
  35. return this;
  36. }
  37.  
  38. this.wsId = ++wsIdIncr;
  39. this.shellExt = new ShellExt()
  40. this.hookMain()
  41. }
  42.  
  43. shouldHook(url) {
  44. this.url = url;
  45. return typeof TARGET_URL === 'string'
  46. ? url.includes(TARGET_URL)
  47. : TARGET_URL.test(url);
  48. }
  49.  
  50. hookMain() {
  51. console.log(`Hook-WS [${this.wsId}]:`, this.url);
  52. this.hookOnMessage(this.onMessage.bind(this));
  53. this.hookSend(this.onSend.bind(this));
  54. }
  55.  
  56. onMessage(event) {
  57. // console.log('接收:', event.data);
  58. if (!this.shellExt.openCollectLog()) {
  59. return;
  60. }
  61.  
  62. // 过滤回车
  63. if (event.data === '1DQo=') {
  64. return;
  65. }
  66.  
  67. let base64Result = atob(event.data.substring(1));
  68.  
  69. // 过滤:]
  70. if (base64Result.startsWith("]0;") && base64Result.includes("")) {
  71. console.log("过滤数据", base64Result)
  72. return;
  73. }
  74. // 替换颜色
  75. let result1 = base64Result.replaceAll(/\[\d+;*\d+m/g, "");
  76. // 查找字符串前缀颜色
  77. let result2 = result1.replaceAll(/\[K/g, "");
  78. let result3 = result2.replaceAll(/\[m/g, "");
  79. // 解码中文base64结果
  80. let result = this.decodeBase64(result3)
  81. // console.log("解析数据\n", result)
  82. this.shellExt.collectResult(result)
  83. }
  84.  
  85. decodeBase64(binaryString) {
  86. const bytes = new Uint8Array(binaryString.length);
  87. for (let i = 0; i < binaryString.length; i++) {
  88. bytes[i] = binaryString.charCodeAt(i);
  89. }
  90. // 将二进制数据解码为文本
  91. return new TextDecoder("utf-8").decode(bytes);
  92. }
  93.  
  94. onSend(data) {
  95. // console.log('发送:', data);
  96. // 发送回车,从等待采集变更为采集中
  97. if (this.shellExt.status === 1 && data === "0DQ==") {
  98. this.shellExt.changeBtnStatus()
  99. }
  100. }
  101.  
  102. hookOnMessage(onMessage) {
  103. // 保存原始的onmessage
  104. const originalOnMessage = this.onmessage;
  105. // Hook onmessage
  106. Object.defineProperty(this, 'onmessage', {
  107. set: function (fn) {
  108. return this.addEventListener('message', function (event) {
  109. onMessage(event);
  110. // 执行原始的消息处理
  111. fn.call(this, event);
  112. });
  113. },
  114. get: function () {
  115. return originalOnMessage;
  116. }
  117. });
  118. }
  119.  
  120. hookSend(onSend) {
  121. // Hook send
  122. const originalSend = this.send;
  123. this.send = function (data) {
  124. onSend(data)
  125. // 执行原始的发送操作
  126. return originalSend.call(this, data);
  127. };
  128. }
  129. }
  130.  
  131. class ShellExt {
  132.  
  133. collectLogBtn = null
  134. status = 0;
  135. result = "";
  136.  
  137. constructor() {
  138. this.renderCollectBtn()
  139. }
  140.  
  141.  
  142. openCollectLog() {
  143. return this.status === 2
  144. }
  145.  
  146. renderCollectBtn() {
  147. let btnCssText = "display: inline-block;border-radius: 4px;background: #e5f8f8;color: #00a6a7; text-decoration: none;padding: 6px 12px;cursor: pointer";
  148. let collectLogBtn = DOMApi.createTag("div", "采集日志", btnCssText);
  149. collectLogBtn.classList.add("collect-log-btn");
  150. this.collectLogBtn = collectLogBtn
  151. DOMApi.eventListener(collectLogBtn, "click", () => {
  152. this.collectLogHandler()
  153. })
  154.  
  155. setTimeout(() => {
  156. let bottomTitleList = document.querySelectorAll(".title.clearfix");
  157. bottomTitleList.forEach(bottomTitle => {
  158. let collectBtn = bottomTitle.querySelector(".collect-log-btn");
  159. if (collectBtn) {
  160. return;
  161. }
  162. bottomTitle.append(this.collectLogBtn)
  163. })
  164. }, 500)
  165. }
  166.  
  167. collectLogHandler() {
  168. if (this.status === 2) {
  169. this.copyToClipboard(this.result)
  170. this.result = ''
  171. }
  172. this.changeBtnStatus()
  173. }
  174.  
  175. collectResult(segmentStr) {
  176. if (!this.openCollectLog()) {
  177. return;
  178. }
  179.  
  180. this.result += segmentStr;
  181. }
  182.  
  183. changeBtnStatus() {
  184. // 未开启
  185. if (this.status === 0) {
  186. this.collectLogBtn.innerHTML = "等待回车开始采集"
  187. this.collectLogBtn.style.backgroundColor = "rgb(251,224,224)";
  188. this.collectLogBtn.style.color = "rgb(191,110,75)";
  189. this.status = 1
  190. // 等待采集
  191. } else if (this.status === 1) {
  192. this.collectLogBtn.innerHTML = "采集中"
  193. this.collectLogBtn.style.backgroundColor = "rgb(184,53,53)";
  194. this.collectLogBtn.style.color = "rgb(57,40,40)";
  195. this.status = 2
  196. // 采集中
  197. } else if (this.status === 2) {
  198. this.collectLogBtn.innerHTML = "采集日志"
  199. this.collectLogBtn.style.backgroundColor = "rgb(215,254,195)";
  200. this.collectLogBtn.style.color = "rgb(2,180,6)";
  201. this.status = 0
  202. }
  203. }
  204.  
  205. copyToClipboard(text) {
  206. if (navigator.clipboard) {
  207. navigator.clipboard.writeText(text).then(() => {
  208. console.log('Text copied to clipboard');
  209. this.showMessage("日志内容已经复制到剪切板", 2000)
  210. }).catch(err => {
  211. console.error('Failed to copy text to clipboard', err);
  212. });
  213. } else {
  214. console.log('Clipboard API not available');
  215. }
  216. }
  217.  
  218. showMessage(text, duration) {
  219. // 创建一个div元素作为消息提示
  220. let message = document.createElement('div');
  221. message.style.position = 'fixed';
  222. message.style.top = '15%';
  223. message.style.left = '50%';
  224. message.style.transform = 'translate(-50%, -50%)';
  225. message.style.padding = '10px';
  226. message.style.border = '1px solid #ccc';
  227. message.style.backgroundColor = '#e1dcdc';
  228. message.style.zIndex = '1000';
  229. message.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
  230. message.style.borderRadius = '5px';
  231. message.style.fontSize = '16px';
  232. message.style.fontWeight = 'bold';
  233. message.style.color = '#ba6868';
  234. message.style.textAlign = 'center';
  235. message.style.maxWidth = '300px';
  236. message.style.boxSizing = 'border-box';
  237. message.style.visibility = 'hidden'; // 初始不可见
  238.  
  239. // 设置消息文本
  240. message.textContent = text;
  241.  
  242. // 将消息元素添加到body中
  243. document.body.appendChild(message);
  244.  
  245. // 显示消息
  246. message.style.visibility = 'visible';
  247.  
  248. // 指定时间后消失
  249. setTimeout(function () {
  250. message.style.visibility = 'hidden';
  251. setTimeout(function () {
  252. document.body.removeChild(message);
  253. }, 300); // 等待300毫秒后移除元素,以确保过渡效果
  254. }, duration);
  255. }
  256. }
  257.  
  258. class DOMApi {
  259.  
  260. static createTag(tag, name, style) {
  261. let htmlTag = document.createElement(tag);
  262. if (name) {
  263. htmlTag.innerHTML = name;
  264. }
  265. if (style) {
  266. htmlTag.style.cssText = style;
  267. }
  268. return htmlTag;
  269. }
  270.  
  271. static eventListener(tag, eventType, func) {
  272. tag.addEventListener(eventType, func)
  273. }
  274. }
  275.  
  276. window.WebSocket = WebSocketProxy;
  277. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址