|
|
@@ -0,0 +1,411 @@
|
|
|
+<template>
|
|
|
+ <el-container>
|
|
|
+ <el-header height="220">
|
|
|
+ <el-row style="margin-top: 10px">
|
|
|
+ <span>
|
|
|
+ <span v-if="wsConnectStatus" style="color: green; margin: 5px">WebSocket 已连接</span>
|
|
|
+ <span v-if="!wsConnectStatus" style="color: red; margin: 5px">WebSocket 未连接</span>
|
|
|
+ </span>
|
|
|
+ <el-select
|
|
|
+ v-model="env"
|
|
|
+ size="mini"
|
|
|
+ placeholder="环境"
|
|
|
+ style="margin-left: 5px"
|
|
|
+ @change="onSelectChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="(item, index) in envList"
|
|
|
+ :key="index"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-select
|
|
|
+ v-model="machineId"
|
|
|
+ size="mini"
|
|
|
+ placeholder="机器"
|
|
|
+ style="margin-left: 5px"
|
|
|
+ @change="onSelectChange1"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="(item, index) in machineList"
|
|
|
+ :key="index"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-button size="mini" type="warning" icon="el-icon-refresh" style="margin-left: 5px" @click="onRefresh">刷新</el-button>
|
|
|
+ <el-button size="mini" type="warning" icon="el-icon-files" style="margin-left: 5px" @click="onGetImages">镜像列表</el-button>
|
|
|
+ </el-row>
|
|
|
+ </el-header>
|
|
|
+ <el-main>
|
|
|
+ <el-row>
|
|
|
+ <div id="chart1" style="height:400px;" />
|
|
|
+ </el-row>
|
|
|
+ </el-main>
|
|
|
+
|
|
|
+ <el-dialog
|
|
|
+ title="镜像列表"
|
|
|
+ append-to-body
|
|
|
+ :visible.sync="showImageDialog"
|
|
|
+ width="70%"
|
|
|
+ center
|
|
|
+ >
|
|
|
+ <template>
|
|
|
+ <el-select
|
|
|
+ v-model="queryInfo.type"
|
|
|
+ size="mini"
|
|
|
+ style="margin-left: 5px"
|
|
|
+ >
|
|
|
+ <el-option label="关键字匹配" value="1" />
|
|
|
+ <el-option label="前缀匹配" value="2" />
|
|
|
+ </el-select>
|
|
|
+ <el-input
|
|
|
+ v-model="queryInfo.keyword"
|
|
|
+ size="mini"
|
|
|
+ style="width: 20%; padding: 3px"
|
|
|
+ clearable
|
|
|
+ placeholder="标题"
|
|
|
+ />
|
|
|
+ <el-button size="mini" type="success" icon="el-icon-search" style="margin-left: 5px" @click="onGetImages">查询</el-button>
|
|
|
+ <el-button size="mini" type="danger" icon="el-icon-search" style="margin-left: 5px" @click="onDeleteImages">删除未用镜像</el-button>
|
|
|
+ <el-table
|
|
|
+ ref="multipleTable"
|
|
|
+ :data="dataList1"
|
|
|
+ border
|
|
|
+ height="480"
|
|
|
+ style="margin-left: 5px; width: 100%"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ type="selection"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ prop="repoTag"
|
|
|
+ label="镜像标签"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ prop="createdAt"
|
|
|
+ label="创建时间"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ prop="totalContainers"
|
|
|
+ label="使用的容器数量"
|
|
|
+ />
|
|
|
+ </el-table>
|
|
|
+ <el-button size="mini" type="success" icon="el-icon-minus" style="margin: 5px" @click="toggleSelection()">取消已选择</el-button>
|
|
|
+ <el-button size="mini" type="success" icon="el-icon-delete" style="margin: 5px" @click="deleteSelection()">删除已选择</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </el-container>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getEnvList, getMachineSessions } from '@/api/devops'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'NginxLog',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ env: 'test',
|
|
|
+ envList: [],
|
|
|
+ machineId: '',
|
|
|
+ machineList: [],
|
|
|
+ queryInfo: {
|
|
|
+ type: '1',
|
|
|
+ keyword: ''
|
|
|
+ },
|
|
|
+ multipleSelection: [],
|
|
|
+ dataList: [],
|
|
|
+ dockerContainerList: [],
|
|
|
+ showImageDialog: false,
|
|
|
+ dataList1: [],
|
|
|
+ wsClient: null,
|
|
|
+ wsConnectStatus: false,
|
|
|
+ wsReconnectLock: false,
|
|
|
+ myChart: null,
|
|
|
+ xData: [],
|
|
|
+ yData: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initChart()
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ document.title = 'NginxLog'
|
|
|
+
|
|
|
+ getEnvList().then(resp => {
|
|
|
+ if (resp.code === 0) {
|
|
|
+ this.env = resp.data.userEnv
|
|
|
+ this.envList = resp.data.envList
|
|
|
+ this.getMachineList(this.env)
|
|
|
+ } else {
|
|
|
+ this.$message.error(resp.msg)
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error(error.message)
|
|
|
+ })
|
|
|
+ this.initWebSocket()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getMachineList(env) {
|
|
|
+ getMachineSessions(env).then(resp => {
|
|
|
+ if (resp.code === 0) {
|
|
|
+ this.machineList = resp.data
|
|
|
+ } else {
|
|
|
+ this.$message.error(resp.msg)
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error(error.message)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handleSelectionChange(val) {
|
|
|
+ this.multipleSelection = val
|
|
|
+ },
|
|
|
+ toggleSelection(rows) {
|
|
|
+ if (rows) {
|
|
|
+ rows.forEach(row => {
|
|
|
+ this.$refs.multipleTable.toggleRowSelection(row)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.$refs.multipleTable.clearSelection()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deleteSelection() {
|
|
|
+ if (this.multipleSelection.length === 0) {
|
|
|
+ this.$message.warning('请先选择要删除的项')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const imageIds = []
|
|
|
+ for (const item of this.multipleSelection) {
|
|
|
+ imageIds.push(item.imageId)
|
|
|
+ }
|
|
|
+ this.$confirm('确定要删除选择的镜像?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('imageIds', imageIds)
|
|
|
+
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'imageRm'
|
|
|
+ jsonPayload.payload = imageIds
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message({
|
|
|
+ type: 'info',
|
|
|
+ message: '已取消'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handleDelete(index, row) {
|
|
|
+ this.$confirm('确定要删除选择的容器?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('opsType', 3)
|
|
|
+ formData.append('containerId', row.containerId)
|
|
|
+
|
|
|
+ const containerIds = []
|
|
|
+ containerIds.push(row.containerId)
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'containerRm'
|
|
|
+ jsonPayload.payload = containerIds
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message({
|
|
|
+ type: 'info',
|
|
|
+ message: '已取消'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onRefresh() {
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'containerList'
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ },
|
|
|
+ onGetImages() {
|
|
|
+ if (this.machineId === '') {
|
|
|
+ this.$message.warning('请选择机器')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'imageList'
|
|
|
+ jsonPayload.dockerQuery = this.queryInfo
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ this.showImageDialog = true
|
|
|
+ },
|
|
|
+ onDeleteImages() {
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'imageRmUnused'
|
|
|
+ jsonPayload.dockerQuery = this.queryInfo
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ },
|
|
|
+ onSelectChange() {
|
|
|
+ this.machineId = ''
|
|
|
+ this.getMachineList(this.env)
|
|
|
+ },
|
|
|
+ onSelectChange1() {
|
|
|
+ if (!this.wsConnectStatus) {
|
|
|
+ this.$message.warning('websocket 未连接')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const jsonPayload = {}
|
|
|
+ jsonPayload.machineId = this.machineId
|
|
|
+ jsonPayload.ops = 'containerList'
|
|
|
+ this.sendMessage(JSON.stringify(jsonPayload))
|
|
|
+ },
|
|
|
+ // ****************************************************************************************************************
|
|
|
+ // WebSocket相关
|
|
|
+ // ****************************************************************************************************************
|
|
|
+ initChart() {
|
|
|
+ var option = {
|
|
|
+ title: {
|
|
|
+ text: 'Nginx Log'
|
|
|
+ },
|
|
|
+ tooltip: {},
|
|
|
+ toolbox: {
|
|
|
+ feature: {
|
|
|
+ dataZoom: {
|
|
|
+ yAxisIndex: 'none'
|
|
|
+ },
|
|
|
+ restore: {},
|
|
|
+ saveAsImage: {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['reqs/s']
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: this.xData
|
|
|
+ },
|
|
|
+ yAxis: {},
|
|
|
+ series: [{
|
|
|
+ name: 'reqs/s',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: this.yData
|
|
|
+ }],
|
|
|
+ dataZoom: [{ // 这个是设置滚动条的,可以拉动这个滚动条来改变你的图形的显示比例
|
|
|
+ type: 'slider',
|
|
|
+ show: true, // flase直接隐藏图形
|
|
|
+ xAxisIndex: [0],
|
|
|
+ left: '9%', // 滚动条靠左侧的百分比
|
|
|
+ bottom: -5,
|
|
|
+ start: 0, // 滚动条的起始位置
|
|
|
+ end: 50 // 滚动条的截止位置(按比例分割你的柱状图x轴长度)
|
|
|
+ }]
|
|
|
+ }
|
|
|
+
|
|
|
+ this.myChart = this.$echarts.init(document.getElementById('chart1'))
|
|
|
+ this.myChart.setOption(option)
|
|
|
+ },
|
|
|
+ // ****************************************************************************************************************
|
|
|
+ // WebSocket相关
|
|
|
+ // ****************************************************************************************************************
|
|
|
+ getUrl() {
|
|
|
+ const protocol = location.protocol
|
|
|
+ const hostname = location.hostname
|
|
|
+ const port = location.port
|
|
|
+ let prefix
|
|
|
+ if (protocol === 'https:') {
|
|
|
+ if (port === '' || port === 443) {
|
|
|
+ prefix = 'wss://' + hostname
|
|
|
+ } else {
|
|
|
+ prefix = 'wss://' + hostname + ':' + port
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (port === '' || port === 80) {
|
|
|
+ prefix = 'ws://' + hostname
|
|
|
+ } else {
|
|
|
+ prefix = 'ws://' + hostname + ':' + port
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var params = 'app=logstash&host=localhost'
|
|
|
+ return prefix + '/bgws/log/pull?' + params
|
|
|
+ },
|
|
|
+ initWebSocket() {
|
|
|
+ if ('WebSocket' in window) {
|
|
|
+ const wsUrl = this.getUrl()
|
|
|
+ console.log(wsUrl)
|
|
|
+ this.wsClient = new WebSocket(wsUrl)
|
|
|
+ const that = this
|
|
|
+ this.wsClient.onopen = function() {
|
|
|
+ that.setOnline()
|
|
|
+ }
|
|
|
+ this.wsClient.onclose = function() {
|
|
|
+ that.setOffline()
|
|
|
+ that.reconnect()
|
|
|
+ }
|
|
|
+ this.wsClient.onerror = function() {
|
|
|
+ that.setOffline()
|
|
|
+ console.log('websocket connection error...')
|
|
|
+ that.reconnect()
|
|
|
+ }
|
|
|
+ this.wsClient.onmessage = function(evt) {
|
|
|
+ const message = JSON.parse(evt.data)
|
|
|
+ that.processMessage(message)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 浏览器不支持 WebSocket
|
|
|
+ this.$message.error('您的浏览器不支持 WebSocket!')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setOnline() {
|
|
|
+ this.wsConnectStatus = true
|
|
|
+ },
|
|
|
+ setOffline() {
|
|
|
+ this.wsConnectStatus = false
|
|
|
+ },
|
|
|
+ reconnect() {
|
|
|
+ if (this.wsReconnectLock) return
|
|
|
+ this.wsReconnectLock = true
|
|
|
+ const that = this
|
|
|
+ setTimeout(function() {
|
|
|
+ console.log('websocket reconnecting...')
|
|
|
+ that.initWebSocket()
|
|
|
+ that.wsReconnectLock = false
|
|
|
+ }, 5000)
|
|
|
+ },
|
|
|
+ sendMessage(message) {
|
|
|
+ this.$message.info('send ws msg')
|
|
|
+ // this.wsClient.send(message)
|
|
|
+ },
|
|
|
+ processMessage(message) {
|
|
|
+ console.log(message)
|
|
|
+ var data = message
|
|
|
+ for (const x of data[0]) {
|
|
|
+ this.xData.push(x)
|
|
|
+ }
|
|
|
+ for (const y of data[1]) {
|
|
|
+ this.yData.push(y)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.myChart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: this.xData
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ data: this.yData
|
|
|
+ }]
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+</style>
|