Firmwares.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <template>
  2. <div class="ml20 mt20 mr20 flex-row flex-align-center flex-justify-between">
  3. <div class="flex-row">
  4. <a-button type="primary" @click="sVisible = true">
  5. Click to Upload
  6. </a-button>
  7. <a-modal :visible="sVisible" title="Import Firmware File" :closable="false" @cancel="onCancel" @ok="uploadFile"
  8. centered>
  9. <a-form :rules="rules" ref="formRef" :model="uploadParam" :label-col="{ span: 6 }">
  10. <a-form-item name="status" label="Avaliable" required>
  11. <a-switch v-model:checked="uploadParam.status" />
  12. </a-form-item>
  13. <a-form-item name="device_name" label="Device Name" required>
  14. <a-select style="width: 220px" mode="multiple" placeholder="can choose multiple"
  15. v-model:value="uploadParam.device_name">
  16. <a-select-option v-for="k in DeviceNameEnum" :key="k" :value="k">
  17. {{ k }}
  18. </a-select-option>
  19. </a-select>
  20. </a-form-item>
  21. <a-form-item name="release_note" label="Release Note" required>
  22. <a-textarea v-model:value="uploadParam.release_note" showCount :maxlength="300" />
  23. </a-form-item>
  24. <a-form-item label="File" required>
  25. <a-upload :multiple="false" :before-upload="beforeUpload" :show-upload-list="true" :file-list="fileList"
  26. :remove="removeFile">
  27. <a-button type="primary">
  28. <UploadOutlined />
  29. Import Firmware File
  30. </a-button>
  31. </a-upload>
  32. </a-form-item>
  33. </a-form>
  34. </a-modal>
  35. </div>
  36. <div class="flex-row">
  37. <div class="ml5">
  38. <a-select style="width: 150px" v-model:value="param.firmware_status" @select="getAllFirmwares(pageParam)">
  39. <a-select-option v-for="(key, value) in FirmwareStatusEnum" :key="key" :value="value">
  40. {{ key }}
  41. </a-select-option>
  42. </a-select>
  43. </div>
  44. <div class="ml5">
  45. <a-select style="width: 150px" v-model:value="param.device_name" @select="getAllFirmwares(pageParam)">
  46. <a-select-option v-for="item in deviceNameList" :key="item.label" :value="item.value">
  47. {{ item.label }}
  48. </a-select-option>
  49. </a-select>
  50. </div>
  51. <div class="ml5">
  52. <a-input-search :enter-button="true" v-model:value="param.product_version" placeholder="input search verison"
  53. style="width: 250px" @search="getAllFirmwares(pageParam)" />
  54. </div>
  55. </div>
  56. </div>
  57. <div class="table flex-display flex-column">
  58. <a-table :columns="columns" :data-source="data.firmware" :pagination="paginationProp" @change="refreshData"
  59. row-key="firmware_id" :rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)"
  60. :scroll="{ x: '100%', y: 600 }">
  61. <template #device_name="{ record }">
  62. <div v-for="text in record.device_name" :key="text">
  63. {{ text }}
  64. </div>
  65. </template>
  66. <template #file_size="{ record }">
  67. <div>{{ bytesToSize(record.file_size) }}</div>
  68. </template>
  69. <template #firmware_status="{ record }">
  70. <DeviceFirmwareStatus :firmware="record" />
  71. </template>
  72. <template v-for="col in ['file_name', 'release_note']" #[col]="{ text }" :key="col">
  73. <a-tooltip :title="text">
  74. <span>{{ text }}</span>
  75. </a-tooltip>
  76. </template>
  77. </a-table>
  78. </div>
  79. </template>
  80. <script lang="ts" setup>
  81. import { onMounted, reactive, ref } from 'vue'
  82. import { message, notification } from 'ant-design-vue'
  83. import { UploadOutlined } from '@ant-design/icons-vue'
  84. import { TableState } from 'ant-design-vue/lib/table/interface'
  85. import { IPage } from '/@/api/http/type'
  86. import { getFirmwares, importFirmareFile } from '/@/api/manage'
  87. import DeviceFirmwareStatus from '/@/components/devices/DeviceFirmwareStatus.vue'
  88. import { Firmware, FirmwareQueryParam, FirmwareStatusEnum, DeviceNameEnum, FirmwareUploadParam } from '/@/types/device-firmware'
  89. import { commonColor } from '/@/utils/color'
  90. import { bytesToSize } from '/@/utils/bytes'
  91. import { getWorkspaceId } from '/@/utils'
  92. import moment from 'moment'
  93. interface FirmwareData {
  94. firmware: Firmware[]
  95. }
  96. const columns = [
  97. { title: 'Model', dataIndex: 'device_name', width: 120, ellipsis: true, className: 'titleStyle', slots: { customRender: 'device_name' } },
  98. { title: 'File Name', dataIndex: 'file_name', width: 220, ellipsis: true, className: 'titleStyle', slots: { customRender: 'file_name' } },
  99. { title: 'Firmware Version', dataIndex: 'product_version', width: 180, className: 'titleStyle' },
  100. { title: 'File Size', dataIndex: 'file_size', width: 150, className: 'titleStyle', slots: { customRender: 'file_size' } },
  101. { title: 'Creator', dataIndex: 'username', width: 100, className: 'titleStyle' },
  102. { title: 'Release Date', dataIndex: 'released_time', width: 160, sorter: (a: Firmware, b: Firmware) => a.released_time.localeCompare(b.released_time), className: 'titleStyle' },
  103. { title: 'Release Note', dataIndex: 'release_note', width: 300, ellipsis: true, className: 'titleStyle', slots: { customRender: 'release_note' } },
  104. { title: 'Status', dataIndex: 'firmware_status', width: 100, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
  105. ]
  106. const data = reactive<FirmwareData>({
  107. firmware: []
  108. })
  109. const paginationProp = reactive({
  110. pageSizeOptions: ['20', '50', '100'],
  111. showQuickJumper: true,
  112. showSizeChanger: true,
  113. pageSize: 50,
  114. current: 1,
  115. total: 0
  116. })
  117. const deviceNameList = ref<any[]>([{ label: 'All', value: '' }])
  118. type Pagination = TableState['pagination']
  119. const pageParam: IPage = {
  120. page: 1,
  121. total: 0,
  122. page_size: 50
  123. }
  124. const param = reactive<FirmwareQueryParam>({
  125. product_version: '',
  126. device_name: '',
  127. firmware_status: FirmwareStatusEnum.NONE
  128. })
  129. onMounted(() => {
  130. getAllFirmwares(pageParam)
  131. for (const key in DeviceNameEnum) {
  132. const value = DeviceNameEnum[key]
  133. deviceNameList.value.push({ label: value, value: value })
  134. }
  135. })
  136. function refreshData(page: Pagination) {
  137. pageParam.page = page?.current!
  138. pageParam.page_size = page?.pageSize!
  139. getAllFirmwares(pageParam)
  140. }
  141. function getAllFirmwares(page: IPage) {
  142. getFirmwares(getWorkspaceId(), page, param).then(res => {
  143. const firmwareList: Firmware[] = res.data.list
  144. data.firmware = firmwareList
  145. paginationProp.total = res.data.pagination.total
  146. paginationProp.current = res.data.pagination.page
  147. })
  148. }
  149. const sVisible = ref(false)
  150. const uploadParam = reactive<FirmwareUploadParam>({
  151. device_name: [],
  152. release_note: '',
  153. status: true
  154. })
  155. const rules = {
  156. status: [{ required: true }],
  157. release_note: [{ required: true, message: 'Please input release note.' }],
  158. device_name: [{ required: true, message: 'Please select which models this firmware belongs to.' }]
  159. }
  160. interface FileItem {
  161. uid: string;
  162. name?: string;
  163. status?: string;
  164. response?: string;
  165. url?: string;
  166. }
  167. interface FileInfo {
  168. file: FileItem;
  169. fileList: FileItem[];
  170. }
  171. const fileList = ref<FileItem[]>([])
  172. function beforeUpload(file: FileItem) {
  173. if (!file.name || !file.name?.endsWith('.zip')) {
  174. message.error('Format error. Please select zip file.')
  175. return false
  176. }
  177. fileList.value = [file]
  178. return false
  179. }
  180. const formRef = ref()
  181. function removeFile(file: FileItem) {
  182. fileList.value = []
  183. }
  184. function onCancel() {
  185. formRef.value.resetFields()
  186. fileList.value = []
  187. sVisible.value = false
  188. }
  189. const uploadFile = async () => {
  190. if (fileList.value.length === 0) {
  191. message.error('Please select at least one file.')
  192. }
  193. let uploading: string
  194. formRef.value.validate().then(async () => {
  195. const file: FileItem = fileList.value[0]
  196. const fileData = new FormData()
  197. fileData.append('file', file as any, file.name)
  198. Object.keys(uploadParam).forEach((key) => {
  199. const val = uploadParam[key as keyof FirmwareUploadParam]
  200. if (val instanceof Array) {
  201. val.forEach((value) => {
  202. fileData.append(key, value)
  203. })
  204. } else {
  205. fileData.append(key, val.toString())
  206. }
  207. })
  208. const timestamp = new Date().getTime()
  209. uploading = (file.name ?? 'uploding') + timestamp
  210. notification.open({
  211. key: uploading,
  212. message: `Uploading ${moment().format()}`,
  213. description: `[${file.name}] is uploading... `,
  214. duration: null
  215. })
  216. importFirmareFile(getWorkspaceId(), fileData).then((res) => {
  217. if (res.code === 0) {
  218. notification.success({
  219. message: `Uploaded ${moment().format()}`,
  220. description: `[${file.name}] file uploaded successfully. Duration: ${moment.duration(new Date().getTime() - timestamp).asSeconds()}`,
  221. duration: null
  222. })
  223. getAllFirmwares(pageParam)
  224. } else {
  225. notification.error({
  226. message: `Failed to upload [${file.name}]. Check and try again.`,
  227. description: `Error message: ${res.message} ${moment().format()}`,
  228. style: { color: commonColor.FAIL },
  229. duration: null,
  230. })
  231. }
  232. }).finally(() => {
  233. notification.close(uploading)
  234. })
  235. fileList.value = []
  236. formRef.value.resetFields()
  237. sVisible.value = false
  238. })
  239. }
  240. </script>
  241. <style>
  242. .table {
  243. background-color: white;
  244. margin: 20px;
  245. padding: 20px;
  246. height: 88vh;
  247. }
  248. .table-striped {
  249. background-color: #f7f9fa;
  250. }
  251. .ant-table {
  252. border-top: 1px solid rgb(0, 0, 0, 0.06);
  253. border-bottom: 1px solid rgb(0, 0, 0, 0.06);
  254. }
  255. .ant-table-tbody tr td {
  256. border: 0;
  257. }
  258. .ant-table td {
  259. white-space: nowrap;
  260. }
  261. .ant-table-thead tr th {
  262. background: white !important;
  263. border: 0;
  264. }
  265. th.ant-table-selection-column {
  266. background-color: white !important;
  267. }
  268. .ant-table-header {
  269. background-color: white !important;
  270. }
  271. </style>