GitLab Viewer Publish and Deploy Project

GitLab Viewer Publish and Deploy Project!

当前为 2023-06-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitLab Viewer Publish and Deploy Project
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.091
  5. // @description GitLab Viewer Publish and Deploy Project!
  6. // @author Sean
  7. // @match http://192.168.0.200/fe3project/*
  8. // @match http://192.168.0.200/frontend_pc/project/*
  9. // @icon http://192.168.0.200/assets/favicon-7901bd695fb93edb07975966062049829afb56cf11511236e61bcf425070e36e.png
  10. // @require https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.min.js
  11. // @require https://unpkg.com/element-ui/lib/index.js
  12. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js
  13. // @resource myFontFile https://element.eleme.io/2.11/static/element-icons.535877f.woff
  14. // @grant GM_getResourceURL
  15. // @grant GM_addStyle
  16. // @grant GM_getResourceText
  17. // @resource ElementCSS https://unpkg.com/element-ui/lib/theme-chalk/index.css
  18. // @grant GM_xmlhttpRequest
  19. // @license MIT
  20. // @run-at document-end
  21. // ==/UserScript==
  22.  
  23. (function() {
  24. 'use strict';
  25.  
  26. const fontUrl = 'https://element.eleme.io/2.11/static/element-icons.535877f.woff';
  27.  
  28. // 添加样式规则,将字体应用到指定元素上
  29. GM_addStyle(`
  30. @font-face {
  31. font-family: element-icons;
  32. src: url(${fontUrl}) format("woff");
  33. }
  34. `);
  35.  
  36. GM_addStyle(GM_getResourceText('ElementCSS'));
  37.  
  38. let epointCss = ".epoint-tool {position: fixed; bottom: 0%; right:10px; transform: translateY(-40%);}";
  39. epointCss += ".el-row { padding: 3px 0;} .el-dialog__body .el-tree{min-height: 420px; max-height: 500px;overflow: auto;}";
  40. epointCss += ".deploy-body {height: 306px;}"
  41. epointCss += ".el-loading-spinner {margin-top: -60px;} .el-button--primary:focus {outline: 0 !important;}";
  42. // 添加注入样式
  43. let extraStyleElement = document.createElement("style");
  44. extraStyleElement.innerHTML = epointCss;
  45. document.head.appendChild(extraStyleElement);
  46.  
  47. const MyComponent = {
  48. template: `<div class="epoint-wrap">
  49. <div class="epoint-tool">
  50. <el-row><el-button type="primary" icon="el-icon-search" round @click="viewProject">查看</el-button></el-row>
  51. <el-row>
  52. <el-tooltip content="一键部署到170服务器" placement="top" effect="light">
  53. <el-button type="primary" icon="el-icon-s-unfold" round @click="doDeploy">部署</el-button>
  54. </el-tooltip>
  55. </el-row>
  56. <el-row>
  57. <el-tooltip content="一键发布到项目案例库" placement="top" effect="light">
  58. <el-button type="primary" icon="el-icon-upload" round @click="publish">发布</el-button>
  59. </el-tooltip>
  60. </el-row>
  61. <el-row>
  62. <el-tooltip content="进入 Code Pipeline 管理平台进行更多操作" placement="top" effect="light">
  63. <el-button type="primary" icon="el-icon-attract" round @click="manage">管理</el-button>
  64. </el-tooltip>
  65. </el-row>
  66. </div>
  67. <el-dialog
  68. title="目录结构"
  69. width="900px"
  70. :append-to-body="true"
  71. :visible.sync="dialogVisible"
  72. :before-close="handleClose">
  73. <el-tree
  74. class="filter-tree"
  75. :data="data"
  76. :props="defaultProps"
  77. node-key="id"
  78. default-expand-all
  79. @node-click="handleNodeClick"
  80. v-loading="loadingTree"
  81. element-loading-background="rgba(255, 255, 255, 1)"
  82. element-loading-text="拼命加载中......"
  83. ref="tree">
  84. </el-tree>
  85. </el-dialog>
  86. <el-dialog
  87. title="部署"
  88. width="420px"
  89. :visible.sync="depDialogVisible">
  90. <div class="deploy-body"
  91. v-loading="loading"
  92. element-loading-text="正在打包部署至 170 服务器,请耐心等待"
  93. element-loading-spinner="el-icon-loading">
  94. </div>
  95. </el-dialog>
  96. <el-dialog title="部署提示" width="640px" :visible.sync="dialogDeployVisible">
  97. <el-alert
  98. title="此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?"
  99. type="warning" :closable="false" style="margin-bottom: 20px;">
  100. </el-alert>
  101. <el-form :model="form" ref="form" :rules="rules">
  102. <el-form-item label="框架类型" :label-width="formLabelWidth" prop="frame">
  103. <el-select v-model="form.frame" placeholder="请框架类型">
  104. <el-option v-for="item in framework" :key="item.value" :label="item.label" :value="item.value"></el-option>
  105. </el-select>
  106. </el-form-item>
  107. </el-form>
  108. <div slot="footer" class="dialog-footer">
  109. <el-button @click="dialogDeployVisible = false">取 消</el-button>
  110. <el-button type="primary" @click="doDeploy">确 定</el-button>
  111. </div>
  112. </el-dialog>
  113. </div>`,
  114. data() {
  115. return {
  116. dialogVisible: false, // 查看目录结构弹窗
  117. data: [], // 目录结构树的数据结构
  118. defaultProps: {
  119. children: 'children',
  120. label: 'label'
  121. },
  122. loadingTree: false,
  123. depDialogVisible: false, // 部署中弹窗
  124. loading: false,
  125. projectLibUrl: 'http://192.168.118.60:9999/webapp/pages/caselib/create.html', // 项目案例库地址
  126. projectIsDeployed: false, // 项目是否部署过
  127. projectFtpUrl: '', // ftp路径
  128. projectEntryUrl: '', // 项目案例库发布表单的入口页面
  129. supportDeploy: true, // 项目是否支持部署
  130. dialogDeployVisible: false,
  131. formLabelWidth: '120px',
  132. form: {
  133. frame: ''
  134. },
  135. framework: [
  136. {label: '重构模板', value: 1},
  137. {label: 'f9x1.0', value: 2},
  138. {label: 'f9x2.0', value: 3},
  139. {label: 'f950', value: 4},
  140. {label: 'f950sp1', value: 5},
  141. {label: 'f950sp2', value: 6},
  142. {label: 'f950sp3', value: 7},
  143. {label: 'f951', value: 18},
  144. {label: 'f940', value: 8},
  145. {label: 'f941', value: 10},
  146. {label: 'f942', value: 9},
  147. {label: 'f934', value: 11},
  148. {label: 'f933', value: 12},
  149. {label: 'f932', value: 13},
  150. {label: 'f9211', value: 14},
  151. {label: '骨架', value: 15},
  152. {label: 'Vue', value: 16},
  153. {label: 'React', value: 17}
  154. ],
  155. rules: {
  156. frame: [
  157. { required: true, message: '请选择框架类型', trigger: 'change' }
  158. ]
  159. },
  160. clickNodeEntry: null
  161. };
  162. },
  163. methods: {
  164. handleClose(done) {
  165. done();
  166. /*
  167. this.$confirm('确认关闭?')
  168. .then(_ => {
  169. done();
  170. })
  171. .catch(_ => {});*/
  172. },
  173. handleNodeClick(data) {
  174. console.log(data);
  175. if(data.type === 'folder') {
  176. return false;
  177. }
  178. var self = this;
  179. var entry = data.entry;
  180. self.clickNodeEntry = entry;
  181. if(self.projectFtpUrl) {
  182. window.open('http://192.168.219.170' + self.projectFtpUrl + data.entry);
  183. self.clickNodeEntry = null;
  184. } else {
  185. if(this.supportDeploy === false) {
  186. this.$message({
  187. type: 'error',
  188. message: '当前项目暂时只支持查看,请耐心等待功能升级。'
  189. });
  190. self.clickNodeEntry = null;
  191. return false;
  192. }
  193.  
  194. this.$confirm('资源未部署,部署至 170 服务器后可查看,是否部署?')
  195. .then(_ => {
  196.  
  197. this.dialogDeployVisible = true;
  198. /*
  199. this.depDialogVisible = true;
  200. this.loading = true;
  201. // 部署
  202. this.getDeployInfo({ type: '1' }, (data)=> {
  203. this.loading = false;
  204. this.depDialogVisible = false;
  205.  
  206. this.data = data.custom.detail;
  207. this.projectFtpUrl = data.custom.ftpUrl;
  208.  
  209. this.$alert('部署成功!', '提示', {
  210. confirmButtonText: '确定',
  211. callback: action => {
  212. window.open('http://192.168.219.170' + self.projectFtpUrl + entry)
  213. }
  214. });
  215.  
  216. });*/
  217. })
  218. .catch(_ => {});
  219. }
  220. },
  221. // 部署
  222. doDeploy () {
  223. if(!this.isDownLoadPage()) {
  224. return;
  225. }
  226. if(!this.dialogDeployVisible) {
  227. this.dialogDeployVisible = true;
  228. return;
  229. }
  230.  
  231. let self = this;
  232.  
  233. this.$refs['form'].validate((valid) => {
  234. if (valid) {
  235. this.dialogDeployVisible = false;
  236. this.depDialogVisible = true;
  237. this.loading = true;
  238. // 部署
  239. this.getDeployInfo({ type: '1', frame: this.form.frame }, (data)=> {
  240. this.loading = false;
  241. this.depDialogVisible = false;
  242.  
  243. if(!data.custom.text){
  244.  
  245. this.data = data.custom.pageTreeData;
  246. this.projectFtpUrl = data.custom.projectRootPath;
  247. this.supportDeploy = data.custom.supportDeploy;
  248.  
  249. this.setProjectEntry();
  250. // 从部署按钮直接过来的
  251. if(!this.clickNodeEntry) {
  252. this.$message({
  253. type: 'success',
  254. message: '部署成功!'
  255. });
  256. // 打开查看弹窗
  257. this.viewProject();
  258. } else {// 从点击目录结构过来的,可以调整点击的树节点页面
  259. this.$alert('部署成功!', '提示', {
  260. confirmButtonText: '确定',
  261. callback: action => {
  262. window.open('http://192.168.219.170' + self.projectFtpUrl + self.clickNodeEntry);
  263. self.clickNodeEntry = null;
  264. }
  265. });
  266. }
  267.  
  268. } else {
  269. this.$message({
  270. type: 'error',
  271. message: data.custom.text
  272. });
  273. }
  274. });
  275. } else {
  276. console.log('error submit!!');
  277. return false;
  278. }
  279. });
  280.  
  281. /*
  282. this.$confirm('此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?', '提示', {
  283. confirmButtonText: '确定',
  284. cancelButtonText: '取消',
  285. type: 'warning'
  286. }).then(() => {
  287. }).catch(() => {
  288. this.$message({
  289. type: 'info',
  290. message: '已取消部署'
  291. });
  292. });*/
  293. },
  294. // 发布项目案例库
  295. publish () {
  296. this.$confirm('此操作将把项目发布至 <a href="'+ this.projectLibUrl +'" target="_blank">项目案例库</a>,已发布过的项目案例库会有重复项,是否继续?', '提示', {
  297. confirmButtonText: '确定',
  298. cancelButtonText: '取消',
  299. dangerouslyUseHTMLString: true,
  300. type: 'warning'
  301. }).then(() => {
  302. const themes = document.querySelectorAll('.badge-secondary');
  303. let keys = [];
  304.  
  305. for(let i = 0, l = themes.length; i < l; i++) {
  306. if(themes[0].innerText == '智能设备' && i > 0) {
  307. keys.push(themes[i].innerText);
  308. } else if (themes[0].innerText !== '智能设备' && i > 1) {
  309. keys.push(themes[i].innerText);
  310. }
  311. }
  312. // 存在更多主题的情况
  313. const moreKeyEl = document.querySelector('.gl-w-full .text-nowrap');
  314. if(moreKeyEl) {
  315. const moreKeyElContent = moreKeyEl.getAttribute('data-content');
  316. const regex = />([^<]+)</g;
  317. const matches = moreKeyElContent.match(regex);
  318. const results = matches.filter(function(match) {
  319. return match.length > 3;
  320. });
  321. const moreKeyData = results.map(function(match) {
  322. return match.substring(2, match.length - 2);
  323. });
  324.  
  325. if(moreKeyData && moreKeyData.length) {
  326. keys = keys.concat(moreKeyData);
  327. }
  328. }
  329.  
  330. // 组织项目案例库所需参数
  331. const projectName = document.querySelector('.home-panel-title').innerText;
  332. const projectBU = themes[0] ? themes[0].innerText : null;
  333. const projectKeys = keys.join(' ');
  334. const entryUrl = this.projectEntryUrl;
  335. let projectType = themes[1] ? themes[1].innerText : null;
  336.  
  337. if(themes[0]) {
  338. if(themes[0].innerText == '智能设备') {
  339. projectType = themes[1] ? themes[0].innerText : null;
  340. } else {
  341. projectType = themes[1] ? themes[1].innerText : null;
  342. }
  343. }
  344.  
  345. const destUrl = this.projectLibUrl + '?projectName=' + projectName + '&projectBU=' + projectBU + '&projectType=' + projectType + '&projectKeys=' + projectKeys + '&entryUrl=' + entryUrl + '&git=' + window.location.href;
  346.  
  347. this.$message({
  348. type: 'success',
  349. message: destUrl
  350. });
  351.  
  352. window.open(destUrl);
  353.  
  354. }).catch(() => {
  355. this.$message({
  356. type: 'info',
  357. message: '已取消发布'
  358. });
  359. });
  360. },
  361. // 查看项目
  362. viewProject () {
  363. if(!this.isDownLoadPage()) {
  364. return;
  365. }
  366. this.dialogVisible = true;
  367. this.loadingTree = true;
  368. let self = this;
  369.  
  370. // 发送ajax请求,查看是否进行过部署
  371. this.getDeployInfo((data)=> {
  372. // 有部署信息,直接赋值,
  373. if(data) {
  374. self.projectIsDeployed = true;
  375. self.data = data.custom.pageTreeData;
  376. self.projectFtpUrl = data.custom.projectRootPath;
  377. self.supportDeploy = data.custom.supportDeploy;
  378. self.loadingTree = false;
  379.  
  380. self.setProjectEntry();
  381.  
  382. } else {
  383. // 无部署信息,仅查看文件目录
  384. getZipResource((data)=> {
  385. self.data = data;
  386. self.loadingTree = false;
  387. });
  388. }
  389. });
  390.  
  391. },
  392. // 项目部署信息
  393. getDeployInfo(params, callback) {
  394. const projectId = document.body.getAttribute('data-project-id');
  395. const downloadBtn = document.querySelector('.gl-button.btn-sm.btn-confirm');
  396.  
  397. const sourceUrl = downloadBtn.getAttribute('href');
  398. const downloadUrl = window.location.origin + sourceUrl;
  399. const files = document.body.getAttribute('data-find-file').split('/');
  400. const name = document.body.getAttribute('data-project') + '-' + files[files.length - 1];
  401. const author = document.querySelector('.current-user .gl-font-weight-bold').innerText.trim();
  402. const projectName = document.querySelector('.sidebar-context-title').innerText.trim();
  403. const deployManOA = document.querySelector('.current-user>a').getAttribute('data-user');
  404. const projectGitUrl = 'http://192.168.0.200' + document.body.getAttribute('data-find-file').split('/-/')[0];
  405.  
  406. if(typeof params == 'function') {
  407. callback = params;
  408. params = null;
  409. }
  410.  
  411. if(projectId && projectId.length && sourceUrl) {
  412. fetch('http://192.168.219.170:3008/api/getDeployInfo', {
  413. method: 'POST',
  414. // 允许跨域请求
  415. mode: 'cors',
  416. headers: {
  417. 'Accept': 'application/json',
  418. 'Content-Type': 'application/json'
  419. },
  420. body: JSON.stringify({//post请求参数
  421. params: {
  422. "type": (params && params.type !== undefined) ? params.type : '0',// 0 代表查看, 1代表部署
  423. "name": name, // 项目路径英文名
  424. "deployMan": author, // 部署人姓名
  425. "deployManOA": deployManOA, // 部署人账号
  426. "projectName": projectName, // 项目名称
  427. "downloadUrl": downloadUrl, // 下载地址
  428. "projectId": projectId, // 主键,gitlab上的项目id
  429. "projectGitUrl": projectGitUrl,
  430. "frame": (params && params.frame) ? params.frame : undefined
  431. }
  432. })
  433. })
  434. .then(response => response.text())
  435. .then((result) => {
  436. var data = JSON.parse(result);
  437. callback && callback(data);
  438. })
  439. .catch(error => {
  440. this.depDialogVisible = false;
  441. this.dialogVisible = false;
  442. this.$message({
  443. type: 'error',
  444. message: '系统故障,请联系管理员。'
  445. });
  446. console.error(error);
  447. });
  448. } else {
  449. this.$message({
  450. type: 'error',
  451. message: '本页不支持查看和部署,请至仓库页。'
  452. });
  453. console.error('部署信息请求参数error');
  454. }
  455. },
  456. // 进入管理平台 code pipeline
  457. manage() {
  458. window.open('http://192.168.219.170/code-pipeline')
  459. },
  460. // 设置入口
  461. setProjectEntry(){
  462. const firstNode = findFirstFileNode(this.data);
  463. if(firstNode) {
  464. this.projectEntryUrl = 'http://192.168.219.170' + this.projectFtpUrl + firstNode.entry;
  465. }else {
  466. this.projectEntryUrl = null;
  467. }
  468. },
  469. isDownLoadPage() {
  470. const downloadBtn = document.querySelector('.gl-button.btn-sm.btn-confirm');
  471. const sourceUrl = downloadBtn && downloadBtn.getAttribute('href');
  472.  
  473. if(downloadBtn && sourceUrl) {
  474. return true;
  475. } else {
  476. this.$message({
  477. type: 'error',
  478. message: '本页面不支持查看和部署,请移至仓库页。'
  479. });
  480. return false;
  481. }
  482. }
  483. },
  484. mounted() {
  485.  
  486. }
  487. };
  488.  
  489. const placeholder = document.createElement('div');
  490.  
  491. // 创建 Vue 实例并挂载到页面
  492. const vueInstance = new Vue({
  493. el: placeholder,
  494. components: {
  495. MyComponent
  496. },
  497. methods: {
  498. },
  499. template: `<my-component></my-component>`
  500. });
  501.  
  502. // 等待页面加载完成
  503. window.addEventListener('load', function() {
  504. // 将占位元素追加到 body 元素中
  505. document.body.appendChild(vueInstance.$el);
  506. });
  507.  
  508. // 将文件条目组织成嵌套结构
  509. function organizeFileEntries(fileEntries) {
  510. const root = {
  511. label: document.querySelector('.home-panel-title').innerText || document.getElementById('project_name_edit').value,
  512. type: 'folder',
  513. children: []
  514. };
  515.  
  516. // 创建嵌套结构
  517. fileEntries.forEach(entry => {
  518. const pathSegments = entry.name.split('/');
  519. let currentFolder = root;
  520.  
  521. // 遍历路径中的每个部分,创建相应的文件夹节点
  522. for (let i = 0; i < pathSegments.length - 1; i++) {
  523. const folderName = pathSegments[i];
  524. let folder = currentFolder.children.find(child => child.label === folderName);
  525.  
  526. if(isExcludeFolder(entry.name)) {
  527. continue;
  528. }
  529.  
  530. if (!folder) {
  531. folder = {
  532. label: folderName,
  533. type: 'folder',
  534. children: []
  535. };
  536. currentFolder.children.push(folder);
  537. }
  538.  
  539. currentFolder = folder;
  540. }
  541.  
  542. // 创建文件节点并添加到相应的文件夹中
  543. const fileName = pathSegments[pathSegments.length - 1];
  544.  
  545. if(fileName && fileName.length && isIncludeFile(fileName) && !isExcludeFolder(entry.name)) {
  546. const fileNode = {
  547. label: fileName,
  548. type: 'file',
  549. entry: entry.name
  550. };
  551. currentFolder.children.push(fileNode);
  552. }
  553. });
  554.  
  555. return [root];
  556. }
  557. // 是否排除的文件夹
  558. function isExcludeFolder(entry) {
  559. if(entry.indexOf('css') > -1 ||
  560. entry.indexOf('scss') > -1 ||
  561. entry.indexOf('js') > -1 ||
  562. entry.indexOf('images') > -1 ||
  563. entry.indexOf('fui') > -1 ||
  564. entry.indexOf('lib') > -1 ||
  565. entry.indexOf('test') > -1 ||
  566. entry.indexOf('font') > -1 ||
  567. entry.indexOf('frame/fui') > -1) {
  568. return true;
  569. } else {
  570. return false;
  571. }
  572. }
  573. // 是否包含的文件
  574. function isIncludeFile(fileName) {
  575. if(fileName.indexOf('.html') > -1) {
  576. return true;
  577. } else {
  578. return false;
  579. }
  580. }
  581.  
  582. function getZipResource(callback) {
  583. const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href');
  584.  
  585. fetch(downloadUrl)
  586. .then(response => response.arrayBuffer())
  587. .then(data => {
  588. // 将 ZIP 文件的二进制数据传递给 JSZip 进行解析
  589. return JSZip.loadAsync(data);
  590. })
  591. .then(zip => {
  592. // 获取 ZIP 文件中的所有条目(文件和目录)
  593. const zipEntries = Object.values(zip.files);
  594. const treeData = organizeFileEntries(zipEntries)
  595.  
  596. callback && callback(treeData);
  597.  
  598. })
  599. .catch(error => {
  600. console.error(error);
  601. });
  602. }
  603.  
  604. // 树结构第一个节点数据
  605. function findFirstFileNode(tree) {
  606. // 遍历树的节点
  607. for (let i = 0; i < tree.length; i++) {
  608. const node = tree[i];
  609.  
  610. // 如果节点的类型为 file,则返回该节点
  611. if (node.type === 'file') {
  612. return node;
  613. }
  614.  
  615. // 如果节点有子节点,则递归调用该函数查找子节点中的第一个 file 节点
  616. if (node.children && node.children.length > 0) {
  617. const fileNode = findFirstFileNode(node.children);
  618. if (fileNode) {
  619. return fileNode;
  620. }
  621. }
  622. }
  623.  
  624. // 如果没有找到 file 节点,则返回 null
  625. return null;
  626. }
  627. })();