Pipeline.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <ElTabs class="tabs" v-model="activeTab">
  3. <ElTabPane label="流水线" name="pipeline">
  4. <ElTable :data="pipelines" width="100%">
  5. <ElTableColumn
  6. prop="id"
  7. label="ID"
  8. align="center"
  9. width="60"
  10. ></ElTableColumn>
  11. <ElTableColumn
  12. prop="name"
  13. label="名称"
  14. align="center"
  15. width="180"
  16. ></ElTableColumn>
  17. <ElTableColumn
  18. prop="username"
  19. label="最近执行人"
  20. align="center"
  21. width="180"
  22. ></ElTableColumn>
  23. <ElTableColumn
  24. label="最近执行时间"
  25. align="center"
  26. :formatter="(row) => timeFormatter(row, 'startTime')"
  27. ></ElTableColumn>
  28. <ElTableColumn
  29. prop="result"
  30. label="执行状态"
  31. align="center"
  32. width="100"
  33. :filters="[{ text: '成功', value: '成功' }, { text: '失败', value: '失败' }]"
  34. :filter-method="(value, row) => {
  35. return row.result === value;
  36. }"
  37. filter-placement="bottom-end"
  38. >
  39. <template #default="scope">
  40. <template v-if="scope.row.result === '成功'">
  41. <ElTag type="success">{{scope.row.result}}</ElTag>
  42. </template>
  43. <template v-else-if="scope.row.result === '失败'">
  44. <ElTag type="danger">{{scope.row.result}}</ElTag>
  45. </template>
  46. <template v-else-if="scope.row.result === '执行中'">
  47. <ElTag>{{scope.row.result}}<i class="el-icon-loading"></i></ElTag>
  48. </template>
  49. <template v-else>
  50. <ElTag type="warning">未知状态</ElTag>
  51. </template>
  52. </template>
  53. </ElTableColumn>
  54. <ElTableColumn
  55. label="详情"
  56. align="center"
  57. >
  58. <template #default="scope">
  59. <ElButton
  60. @click="handleClick('detail', scope.row.id)"
  61. type="text"
  62. size="small"
  63. :disabled="scope.row.result==='执行中'"
  64. >最近执行详情</ElButton>
  65. <ElButton @click="handleClick('history', scope.row.id)" type="text" size="small">历史</ElButton>
  66. </template>
  67. </ElTableColumn>
  68. <ElTableColumn
  69. align="center"
  70. >
  71. <template #header>
  72. <span>操作</span>
  73. <ElButton @click="handleClick('create')" type="text" class="add-button">新建</ElButton>
  74. </template>
  75. <template #default="scope">
  76. <ElButton @click="handleClick('trigger', scope.row.id)" type="text" size="small">触发</ElButton>
  77. <ElButton @click="handleClick('edit', scope.row.id)" type="text" size="small">编辑</ElButton>
  78. <ElButton @click="handleClick('delete', scope.row.id)" type="text" size="small">删除</ElButton>
  79. </template>
  80. </ElTableColumn>
  81. </ElTable>
  82. <ElDialog v-model="showPipelineDetailModal" destroy-on-close>
  83. <template #title>最近一次流水线构建的执行结果详情</template>
  84. <pre class="pipeline-detail">{{detail}}</pre>
  85. </ElDialog>
  86. <ElDialog v-model="showPipelineHistoryModal" destroy-on-close>
  87. <template #title>流水线构建历史</template>
  88. <ElTable
  89. :data="pipelineHistory"
  90. :default-sort="{
  91. props: 'id',
  92. }"
  93. height="400"
  94. >
  95. <ElTableColumn type="expand">
  96. <template #default="scope">
  97. <pre class="pipeline-detail">{{scope.row.details}}</pre>
  98. </template>
  99. </ElTableColumn>
  100. <ElTableColumn
  101. prop="id"
  102. label="构建ID"
  103. align="center"
  104. width="100"
  105. ></ElTableColumn>
  106. <ElTableColumn
  107. prop="pipelineId"
  108. label="流水线ID"
  109. align="center"
  110. width="100"
  111. ></ElTableColumn>
  112. <ElTableColumn
  113. prop="username"
  114. label="触发人"
  115. align="center"
  116. width="180"
  117. ></ElTableColumn>
  118. <ElTableColumn
  119. label="执行时间"
  120. align="center"
  121. :formatter="(row) => timeFormatter(row, 'startTime')"
  122. ></ElTableColumn>
  123. </ElTable>
  124. </ElDialog>
  125. <ElDialog
  126. v-model="showPipelineEditModal"
  127. :before-close="(done) => {
  128. jsonValid=true;
  129. done();
  130. }"
  131. destroy-on-close
  132. >
  133. <template #title>
  134. <span>编辑流水线配置</span>
  135. <span class="button-group">
  136. <ElButton type="text">脚本教程</ElButton>
  137. <ElButton type="text">模板详解</ElButton>
  138. </span>
  139. </template>
  140. <ElInput
  141. type="textarea"
  142. :rows="16"
  143. placeholder="请输入流水线配置"
  144. @input="lintJson"
  145. v-model="pipelineConfig">
  146. ></ElInput>
  147. <pre v-if="!jsonValid" :innerText="hint"></pre>
  148. <template #footer>
  149. <span class="dialog-footer">
  150. <el-button type="primary" @click="handlePipelineUpdateConfirm" :disabled="!jsonValid">确 定</el-button>
  151. </span>
  152. </template>
  153. </ElDialog>
  154. <ElDialog
  155. v-model="showPipelineCreateModal"
  156. :before-close="(done) => {
  157. $refs['createPipelineForm'].resetFields();
  158. done();
  159. }"
  160. destroy-on-close
  161. >
  162. <template #title>创建流水线</template>
  163. <ElForm
  164. :model="createPipelineForm"
  165. ref="createPipelineForm"
  166. label-position="top"
  167. label-suffix=":"
  168. class="create-pipeline-form"
  169. >
  170. <ElFormItem label="名称" prop="name" required>
  171. <ElInput v-model="createPipelineForm.name" placeholder="请输入流水线名称"></ElInput>
  172. </ElFormItem>
  173. <ElFormItem label="模板" prop="template" required>
  174. <ElSelect v-model="createPipelineForm.template" placeholder="请选择模板">
  175. <ElOption
  176. v-for="(template, index) in templates"
  177. :key="index" :label="template"
  178. :value="template"
  179. ></ElOption>
  180. </ElSelect>
  181. </ElFormItem>
  182. </ElForm>
  183. <template #footer>
  184. <span class="dialog-footer">
  185. <el-button type="primary" @click="handlePipelineCreateConfirm">确 定</el-button>
  186. </span>
  187. </template>
  188. </ElDialog>
  189. </ElTabPane>
  190. <ElTabPane label="部署产物" name="production">
  191. <ElTable :data="productions" width="100%">
  192. <ElTableColumn
  193. prop="id"
  194. label="ID"
  195. align="center"
  196. width="60"
  197. ></ElTableColumn>
  198. <ElTableColumn
  199. prop="name"
  200. label="名称"
  201. align="center"
  202. ></ElTableColumn>
  203. <ElTableColumn
  204. prop="pipelineId"
  205. label="流水线ID"
  206. align="center"
  207. width="100"
  208. ></ElTableColumn>
  209. <ElTableColumn
  210. label="部署时间"
  211. align="center"
  212. :formatter="(row) => timeFormatter(row, 'deployedTime')"
  213. ></ElTableColumn>
  214. <ElTableColumn
  215. label="URL"
  216. align="center"
  217. >
  218. <template #default="scope">
  219. <ElButton type="text" @click="open(scope.row.accessUrl, '_blank')">
  220. {{scope.row.accessUrl}}
  221. </ElButton>
  222. </template>
  223. </ElTableColumn>
  224. <ElTableColumn
  225. label="运行状态"
  226. align="center"
  227. >
  228. <template #default="scope">
  229. <template v-if="scope.row.status === '正常'">
  230. <ElTag type="success">{{scope.row.status}}</ElTag>
  231. </template>
  232. <template v-else>
  233. <ElTag type="danger">{{scope.row.status}}</ElTag>
  234. </template>
  235. </template>
  236. </ElTableColumn>
  237. <ElTableColumn>
  238. <template #default="scope">
  239. <ElButton type="text" @click="deleteProduction(scope.row.id)">
  240. <i class="el-icon-delete-solid"></i>
  241. </ElButton>
  242. </template>
  243. </ElTableColumn>
  244. </ElTable>
  245. </ElTabPane>
  246. </ElTabs>
  247. </template>
  248. <script>
  249. import jsonlint from '@/utils/jsonlint';
  250. import { PipelineAPI } from '@/api';
  251. import debounce from '@/utils/debounce';
  252. export default {
  253. data() {
  254. return {
  255. activeTab: 'pipeline',
  256. pipelines: [],
  257. productions: [],
  258. showPipelineCreateModal: false,
  259. showPipelineEditModal: false,
  260. showPipelineHistoryModal: false,
  261. showPipelineDetailModal: false,
  262. detail: '',
  263. pipelineHistory: [],
  264. pipelineConfig: '',
  265. jsonValid: true,
  266. editingPipelineId: -1,
  267. templates: ['Java项目', 'MySQL'],
  268. createPipelineForm: {
  269. name: '',
  270. template: '',
  271. },
  272. };
  273. },
  274. async mounted() {
  275. this.pipelines = await PipelineAPI.getPipelinesByProjectId(this.$store.state.workspace.currentProjectId);
  276. this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
  277. },
  278. methods: {
  279. timeFormatter: (row, property) => `${row[property].year}/${row[property].month}/${row[property].day} `
  280. + `${row[property].hours}:${row[property].minutes}:${row[property].seconds}`,
  281. async handleClick(action, id) {
  282. switch (action) {
  283. case 'create': {
  284. this.showPipelineCreateModal = true;
  285. break;
  286. }
  287. case 'trigger': {
  288. await PipelineAPI.triggerDeployment({
  289. projectId: this.$store.state.workspace.currentProjectId,
  290. pipelineId: id,
  291. });
  292. this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
  293. break;
  294. }
  295. case 'detail': {
  296. this.showPipelineDetailModal = true;
  297. this.detail = Array.prototype.find.call(this.pipelines, (pipeline) => pipeline.id === id).details;
  298. break;
  299. }
  300. case 'history': {
  301. this.showPipelineHistoryModal = true;
  302. this.pipelineHistory = await PipelineAPI.getPipelineHistory({
  303. projectId: this.$store.state.workspace.currentProjectId,
  304. pipelineId: id,
  305. });
  306. break;
  307. }
  308. case 'edit': {
  309. this.showPipelineEditModal = true;
  310. this.editingPipelineId = id;
  311. this.pipelineConfig = await PipelineAPI.getPipeline({
  312. projectId: this.$store.state.workspace.currentProjectId,
  313. pipelineId: id,
  314. }).configJson || '{}';
  315. break;
  316. }
  317. case 'delete': {
  318. PipelineAPI.deletePipeline({
  319. projectId: this.$store.state.workspace.currentProjectId,
  320. pipelineId: id,
  321. });
  322. break;
  323. }
  324. default: {
  325. console.warn('Unknown type of action.');
  326. }
  327. }
  328. },
  329. // eslint-disable-next-line func-names
  330. // FIXME: debounce效果有点问题,待排查
  331. lintJson: debounce(function (value) {
  332. try {
  333. const result = jsonlint.parse(value);
  334. this.pipelineConfig = JSON.stringify(result, null, ' ');
  335. this.jsonValid = true;
  336. } catch (e) {
  337. this.jsonValid = false;
  338. this.hint = e;
  339. }
  340. }, 500),
  341. async handlePipelineUpdateConfirm() {
  342. await PipelineAPI.updatePipeline({
  343. projectId: this.$store.state.workspace.currentProjectId,
  344. pipelineId: this.editingPipelineId,
  345. configJson: JSON.stringify(JSON.parse(this.pipelineConfig)),
  346. });
  347. this.showPipelineEditModal = false;
  348. },
  349. async handlePipelineCreateConfirm() {
  350. this.$refs.createPipelineForm.validate(async (valid) => {
  351. if (valid) {
  352. await PipelineAPI.createPipeline({
  353. projectId: this.$store.state.workspace.currentProjectId,
  354. pipelineId: this.editingPipelineId,
  355. name: this.createPipelineForm.name,
  356. templateName: this.createPipelineForm.template,
  357. });
  358. this.showPipelineCreateModal = false;
  359. }
  360. });
  361. },
  362. open(url, target) {
  363. window.open(url, target);
  364. },
  365. async deleteProduction(id) {
  366. await PipelineAPI.deleteInstance(id);
  367. },
  368. },
  369. };
  370. </script>
  371. <style scoped>
  372. .tabs {
  373. width: 80%;
  374. margin: auto auto;
  375. }
  376. .add-button {
  377. margin-left: 14px;
  378. }
  379. .pipeline-detail {
  380. width: 100%;
  381. word-break: break-word;
  382. white-space: pre-wrap;
  383. }
  384. .create-pipeline-form {
  385. width: 60%;
  386. margin: 0 auto;
  387. }
  388. .button-group {
  389. margin-left: 40px;
  390. }
  391. </style>