Browse Source

add MyPeer for user

Tao Chen 1 year ago
parent
commit
cbcfe9365a
4 changed files with 390 additions and 1 deletions
  1. 7 0
      src/api/user.js
  2. 7 1
      src/router/index.js
  3. 9 0
      src/utils/i18n/zh_CN.json
  4. 367 0
      src/views/my/peer/index.vue

+ 7 - 0
src/api/user.js

@@ -75,6 +75,13 @@ export function myOauth () {
75
   })
75
   })
76
 }
76
 }
77
 
77
 
78
+export function myPeer (params) {
79
+  return request({
80
+    url: '/user/myPeer',
81
+    params,
82
+  })
83
+}
84
+
78
 export function groupUsers (data) {
85
 export function groupUsers (data) {
79
   return request({
86
   return request({
80
     url: '/user/groupUsers',
87
     url: '/user/groupUsers',

+ 7 - 1
src/router/index.js

@@ -58,9 +58,15 @@ export const asyncRoutes = [
58
       {
58
       {
59
         path: '/',
59
         path: '/',
60
         name: 'MyInfo',
60
         name: 'MyInfo',
61
-        meta: { title: 'Info', icon: 'User' /*keepAlive: true*/ },
61
+        meta: { title: 'Userinfo', icon: 'User' /*keepAlive: true*/ },
62
         component: () => import('@/views/my/info.vue'),
62
         component: () => import('@/views/my/info.vue'),
63
       },
63
       },
64
+      {
65
+        path: 'peer',
66
+        name: 'MyPeer',
67
+        meta: { title: 'MyPeer', icon: 'Monitor' /*keepAlive: true*/ },
68
+        component: () => import('@/views/my/peer/index.vue'),
69
+      },
64
       {
70
       {
65
         path: 'address_book_collection',
71
         path: 'address_book_collection',
66
         name: 'MyAddressBookCollection',
72
         name: 'MyAddressBookCollection',

+ 9 - 0
src/utils/i18n/zh_CN.json

@@ -460,5 +460,14 @@
460
   },
460
   },
461
   "Email": {
461
   "Email": {
462
     "One": "邮箱"
462
     "One": "邮箱"
463
+  },
464
+  "MyPeer": {
465
+    "One": "我的设备"
466
+  },
467
+  "View": {
468
+    "One": "查看"
469
+  },
470
+  "Information": {
471
+    "One": "信息"
463
   }
472
   }
464
 }
473
 }

+ 367 - 0
src/views/my/peer/index.vue

@@ -0,0 +1,367 @@
1
+<template>
2
+  <div>
3
+    <el-card class="list-query" shadow="hover">
4
+      <el-form inline label-width="150px">
5
+        <el-form-item label="ID">
6
+          <el-input v-model="listQuery.id" clearable/>
7
+        </el-form-item>
8
+        <el-form-item :label="T('Hostname')">
9
+          <el-input v-model="listQuery.hostname" clearable/>
10
+        </el-form-item>
11
+        <el-form-item :label="T('LastOnlineTime')">
12
+          <el-select v-model="listQuery.time_ago" clearable>
13
+            <el-option
14
+                v-for="item in timeFilters"
15
+                :key="item.value"
16
+                :label="item.text"
17
+                :value="item.value"
18
+                :disabled="item.value === 0"
19
+            ></el-option>
20
+          </el-select>
21
+        </el-form-item>
22
+        <el-form-item >
23
+          <el-button type="primary" @click="handlerQuery">{{ T('Filter') }}</el-button>
24
+          <el-button type="success" @click="toExport">{{ T('Export') }}</el-button>
25
+          <el-button type="danger" @click="toBatchDelete">{{ T('BatchDelete') }}</el-button>
26
+        </el-form-item>
27
+      </el-form>
28
+    </el-card>
29
+    <el-card class="list-body" shadow="hover">
30
+      <el-table :data="listRes.list" v-loading="listRes.loading" border size="small" @selection-change="handleSelectionChange">
31
+        <el-table-column type="selection" width="55" align="center"/>
32
+        <el-table-column prop="id" label="ID" align="center" width="150">
33
+          <template #default="{row}">
34
+            <span>{{ row.id }} <el-icon @click="handleClipboard(row.id, $event)"><CopyDocument/></el-icon></span>
35
+          </template>
36
+        </el-table-column>
37
+        <el-table-column prop="cpu" label="CPU" align="center" width="100" show-overflow-tooltip/>
38
+        <el-table-column prop="hostname" :label="T('Hostname')" align="center" width="120"/>
39
+        <el-table-column prop="memory" :label="T('Memory')" align="center" width="120"/>
40
+        <el-table-column prop="os" :label="T('Os')" align="center" width="120" show-overflow-tooltip/>
41
+        <el-table-column prop="last_online_time" :label="T('LastOnlineTime')" align="center" min-width="120">
42
+          <template #default="{row}">
43
+            <div class="last_oline_time">
44
+              <span> {{ row.last_online_time ? timeAgo(row.last_online_time * 1000) : '-' }}</span> <span class="dot" :class="{red: timeDis(row.last_online_time) >= 60, green: timeDis(row.last_online_time)< 60}"></span>
45
+            </div>
46
+          </template>
47
+        </el-table-column>
48
+        <el-table-column prop="last_online_ip" :label="T('LastOnlineIp')" align="center" min-width="120"/>
49
+        <el-table-column prop="username" :label="T('Username')" align="center" width="120"/>
50
+        <el-table-column prop="uuid" :label="T('Uuid')" align="center" width="120" show-overflow-tooltip/>
51
+        <el-table-column prop="version" :label="T('Version')" align="center" width="80"/>
52
+        <el-table-column prop="created_at" :label="T('CreatedAt')" align="center" width="150"/>
53
+        <el-table-column prop="updated_at" :label="T('UpdatedAt')" align="center" width="150"/>
54
+        <el-table-column :label="T('Actions')" align="center" width="500" class-name="table-actions" fixed="right">
55
+          <template #default="{row}">
56
+            <el-button type="success" @click="connectByClient(row.id)">{{ T('Link') }}</el-button>
57
+            <el-button v-if="appStore.setting.appConfig.web_client" type="success" @click="toWebClientLink(row)">Web Client</el-button>
58
+            <el-button type="primary" @click="toAddressBook(row)">{{ T('AddToAddressBook') }}</el-button>
59
+            <el-button @click="toView(row)">{{ T('View') }}</el-button>
60
+            <el-button type="danger" @click="del(row)">{{ T('Delete') }}</el-button>
61
+          </template>
62
+        </el-table-column>
63
+      </el-table>
64
+    </el-card>
65
+    <el-card class="list-page" shadow="hover">
66
+      <el-pagination background
67
+                     layout="prev, pager, next, sizes, jumper"
68
+                     :page-sizes="[10,20,50,100]"
69
+                     v-model:page-size="listQuery.page_size"
70
+                     v-model:current-page="listQuery.page"
71
+                     :total="listRes.total">
72
+      </el-pagination>
73
+    </el-card>
74
+    <el-dialog v-model="formVisible" :title="T('Information')" width="800" :style="{ textAlign: 'center' }" >
75
+      <el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
76
+        <el-form-item label="ID" prop="id" >
77
+          <el-input v-model="formData.id" disabled></el-input>
78
+        </el-form-item>
79
+        <el-form-item :label="T('Username')" prop="username">
80
+          <el-input v-model="formData.username" disabled></el-input>
81
+        </el-form-item>
82
+        <el-form-item :label="T('Hostname')" prop="hostname">
83
+          <el-input v-model="formData.hostname" disabled></el-input>
84
+        </el-form-item>
85
+        <el-form-item label="CPU" prop="cpu">
86
+          <el-input v-model="formData.cpu" disabled></el-input>
87
+        </el-form-item>
88
+        <el-form-item :label="T('Memory')" prop="memory">
89
+          <el-input v-model="formData.memory" disabled></el-input>
90
+        </el-form-item>
91
+        <el-form-item :label="T('Os')" prop="os">
92
+          <el-input v-model="formData.os" disabled></el-input>
93
+        </el-form-item>
94
+        <el-form-item :label="T('Uuid')" prop="uuid">
95
+          <el-input v-model="formData.uuid" disabled></el-input>
96
+        </el-form-item>
97
+        <el-form-item :label="T('Version')" prop="version">
98
+          <el-input v-model="formData.version" disabled></el-input>
99
+        </el-form-item>
100
+      </el-form>
101
+    </el-dialog>
102
+
103
+    <el-dialog v-model="ABFormVisible" width="800" :title="T('Create')">
104
+      <el-form class="dialog-form" ref="form" :model="ABFormData" label-width="120px">
105
+        <el-form-item :label="T('Owner')" prop="user_ids" required>
106
+          <el-select v-model="ABFormData.user_ids" multiple>
107
+            <el-option
108
+                v-for="item in allUsers"
109
+                :key="item.id"
110
+                :label="item.username"
111
+                :value="item.id"
112
+            ></el-option>
113
+          </el-select>
114
+        </el-form-item>
115
+        <el-form-item label="ID" prop="id" required>
116
+          <el-input v-model="ABFormData.id"></el-input>
117
+        </el-form-item>
118
+        <el-form-item :label="T('Username')" prop="username">
119
+          <el-input v-model="ABFormData.username"></el-input>
120
+        </el-form-item>
121
+        <el-form-item :label="T('Alias')" prop="alias">
122
+          <el-input v-model="ABFormData.alias"></el-input>
123
+        </el-form-item>
124
+        <el-form-item :label="T('Hostname')" prop="hostname">
125
+          <el-input v-model="ABFormData.hostname"></el-input>
126
+        </el-form-item>
127
+        <el-form-item :label="T('Platform')" prop="platform">
128
+          <el-select v-model="ABFormData.platform">
129
+            <el-option
130
+                v-for="item in ABPlatformList"
131
+                :key="item.value"
132
+                :label="item.label"
133
+                :value="item.value"
134
+            ></el-option>
135
+          </el-select>
136
+        </el-form-item>
137
+
138
+        <el-form-item :label="T('Tags')" prop="tags">
139
+          <el-select v-model="ABFormData.tags" multiple>
140
+            <el-option
141
+                v-for="item in tagList"
142
+                :key="item.name"
143
+                :label="item.name"
144
+                :value="item.name"
145
+            ></el-option>
146
+          </el-select>
147
+        </el-form-item>
148
+        <el-form-item>
149
+          <el-button @click="ABFormVisible = false">{{ T('Cancel') }}</el-button>
150
+          <el-button @click="ABSubmit" type="primary">{{ T('Submit') }}</el-button>
151
+        </el-form-item>
152
+      </el-form>
153
+    </el-dialog>
154
+  </div>
155
+</template>
156
+
157
+<script setup>
158
+  import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
159
+  import { batchRemove, remove } from '@/api/peer'
160
+  import { myPeer } from '@/api/user'
161
+  import { ElMessage, ElMessageBox } from 'element-plus'
162
+  import { toWebClientLink } from '@/utils/webclient'
163
+  import { T } from '@/utils/i18n'
164
+  import { timeAgo } from '@/utils/time'
165
+  import { jsonToCsv, downBlob } from '@/utils/file'
166
+  import { useRepositories as useABRepositories } from '@/views/address_book/index'
167
+  import { loadAllUsers } from '@/global'
168
+  import { list as fetchTagList } from '@/api/tag'
169
+  import { batchCreate } from '@/api/address_book'
170
+  import { useAppStore } from '@/store/app'
171
+  import { connectByClient } from '@/utils/peer'
172
+  import { CopyDocument } from '@element-plus/icons'
173
+  import { handleClipboard } from '@/utils/clipboard'
174
+
175
+  const appStore = useAppStore()
176
+  const listRes = reactive({
177
+    list: [], total: 0, loading: false,
178
+  })
179
+  const listQuery = reactive({
180
+    page: 1,
181
+    page_size: 10,
182
+    time_ago: null,
183
+    id: '',
184
+    hostname: '',
185
+  })
186
+
187
+  const getList = async () => {
188
+    listRes.loading = true
189
+    const res = await myPeer(listQuery).catch(_ => false)
190
+    listRes.loading = false
191
+    if (res) {
192
+      listRes.list = res.data.list
193
+      listRes.total = res.data.total
194
+    }
195
+  }
196
+  const handlerQuery = () => {
197
+    if (listQuery.page === 1) {
198
+      getList()
199
+    } else {
200
+      listQuery.page = 1
201
+    }
202
+  }
203
+
204
+  const del = async (row) => {
205
+    const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('Delete') }), {
206
+      confirmButtonText: T('Confirm'),
207
+      cancelButtonText: T('Cancel'),
208
+      type: 'warning',
209
+    }).catch(_ => false)
210
+    if (!cf) {
211
+      return false
212
+    }
213
+
214
+    const res = await remove({ row_id: row.row_id }).catch(_ => false)
215
+    if (res) {
216
+      ElMessage.success(T('OperationSuccess'))
217
+      getList()
218
+    }
219
+  }
220
+  onMounted(getList)
221
+  onActivated(getList)
222
+
223
+  watch(() => listQuery.page, getList)
224
+
225
+  watch(() => listQuery.page_size, handlerQuery)
226
+
227
+  const formVisible = ref(false)
228
+  const formData = reactive({
229
+    row_id: 0,
230
+    cpu: '',
231
+    hostname: '',
232
+    id: '',
233
+    memory: '',
234
+    os: '',
235
+    username: '',
236
+    uuid: '',
237
+    version: '',
238
+  })
239
+
240
+  const toView = (row) => {
241
+    formVisible.value = true
242
+    //将row中的数据赋值给formData
243
+    Object.keys(formData).forEach(key => {
244
+      formData[key] = row[key]
245
+    })
246
+  }
247
+  
248
+  const timeDis = (time) => {
249
+    let now = new Date().getTime()
250
+    let after = new Date(time * 1000).getTime()
251
+    return (now - after) / 1000
252
+  }
253
+
254
+  const timeFilters = computed(() => [
255
+    { text: T('MinutesLess', { param: 1 }, 1), value: -60 },
256
+    { text: T('HoursLess', { param: 1 }, 1), value: -3600 },
257
+    { text: T('DaysLess', { param: 1 }, 1), value: -86400 },
258
+    { text: '---------', value: 0 },
259
+    { text: T('MinutesAgo', { param: 1 }, 1), value: 60 },
260
+    { text: T('HoursAgo', { param: 1 }, 1), value: 3600 },
261
+    { text: T('DaysAgo', { param: 1 }, 1), value: 86400 },
262
+    { text: T('MonthsAgo', { param: 1 }, 1), value: 2592000 },
263
+    // { text: T('YearsAgo', { param: 1 }, 1), value: 31536000 },
264
+  ])
265
+
266
+  const toExport = async () => {
267
+    const q = { ...listQuery }
268
+    q.page_size = 10000
269
+    q.page = 1
270
+    const res = await list(q).catch(_ => false)
271
+    if (res) {
272
+      const data = res.data.list.map(item => {
273
+        item.last_online_time = item.last_online_time ? new Date(item.last_online_time * 1000).toLocaleString() : '-'
274
+        delete item.user_id
275
+        delete item.user
276
+        return item
277
+      })
278
+      const csv = jsonToCsv(data)
279
+      downBlob(csv, 'peers.csv')
280
+    }
281
+  }
282
+
283
+  const {
284
+    platformList: ABPlatformList,
285
+    formVisible: ABFormVisible,
286
+    formData: ABFormData,
287
+  } = useABRepositories()
288
+  const toAddressBook = (peer) => {
289
+    ABFormData.id = peer.id
290
+    ABFormData.username = peer.username
291
+    ABFormData.hostname = peer.hostname
292
+    //匹配os
293
+    if (peer.os.indexOf('windows') !== -1) {
294
+      ABFormData.platform = ABPlatformList.find(item => item.label === 'Windows').value
295
+    } else if (peer.os.indexOf('linux') !== -1) {
296
+      ABFormData.platform = ABPlatformList.find(item => item.label === 'Linux').value
297
+    } else if (peer.os.indexOf('android') !== -1) {
298
+      ABFormData.platform = ABPlatformList.find(item => item.label === 'Android').value
299
+    } else if (peer.os.indexOf('mac') !== -1) {
300
+      ABFormData.platform = ABPlatformList.find(item => item.label === 'Mac OS').value
301
+    }
302
+    ABFormData.uuid = peer.uuid
303
+    ABFormVisible.value = true
304
+
305
+  }
306
+  const ABSubmit = async () => {
307
+    const res = await batchCreate(ABFormData).catch(_ => false)
308
+    if (res) {
309
+      ElMessage.success(T('OperationSuccess'))
310
+      ABFormVisible.value = false
311
+    }
312
+  }
313
+
314
+
315
+  const multipleSelection = ref([])
316
+  const handleSelectionChange = (val) => {
317
+    multipleSelection.value = val
318
+  }
319
+  const toBatchDelete = async () => {
320
+    if (!multipleSelection.value.length) {
321
+      ElMessage.warning(T('PleaseSelectData'))
322
+      return false
323
+    }
324
+    const cf = await ElMessageBox.confirm(T('Confirm?', { param: T('BatchDelete') }), {
325
+      confirmButtonText: T('Confirm'),
326
+      cancelButtonText: T('Cancel'),
327
+      type: 'warning',
328
+    }).catch(_ => false)
329
+    if (!cf) {
330
+      return false
331
+    }
332
+
333
+    const res = await batchRemove({ row_ids: multipleSelection.value.map(i => i.row_id) }).catch(_ => false)
334
+    if (res) {
335
+      ElMessage.success(T('OperationSuccess'))
336
+      getList()
337
+    }
338
+  }
339
+</script>
340
+
341
+<style scoped lang="scss">
342
+.list-query .el-select {
343
+  --el-select-width: 180px;
344
+}
345
+
346
+.last_oline_time {
347
+  display: flex;
348
+  justify-content: center;
349
+  align-items: center;
350
+}
351
+
352
+.dot {
353
+  width: 6px;
354
+  height: 6px;
355
+  display: block;
356
+  border-radius: 50%;
357
+  margin-left: 10px;
358
+
359
+  &.red {
360
+    background-color: red;
361
+  }
362
+
363
+  &.green {
364
+    background-color: green;
365
+  }
366
+}
367
+</style>