323 lines
9.9 KiB

<template>
<div class="ml20 mt20 mr20 flex-row flex-align-center flex-justify-between">
<div class="flex-row">
<a-button type="primary" @click="sVisible = true">
Click to Upload
</a-button>
<a-modal :visible="sVisible"
title="Import Firmware File"
:closable="false"
@cancel="onCancel"
@ok="uploadFile"
centered>
<a-form :rules="rules" ref="formRef" :model="uploadParam" :label-col="{ span: 6 }">
<a-form-item name="status" label="Avaliable" required>
<a-switch v-model:checked="uploadParam.status" />
</a-form-item>
<a-form-item name="device_name" label="Device Name" required>
<a-select
style="width: 220px"
mode="multiple"
placeholder="can choose multiple"
v-model:value="uploadParam.device_name">
<a-select-option
v-for="k in DeviceNameEnum"
:key="k"
:value="k"
>
{{ k }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="release_note" label="Release Note" required>
<a-textarea v-model:value="uploadParam.release_note" showCount :maxlength="300" />
</a-form-item>
<a-form-item label="File" required>
<a-upload
:multiple="false"
:before-upload="beforeUpload"
:show-upload-list="true"
:file-list="fileList"
:remove="removeFile"
>
<a-button type="primary">
<UploadOutlined />
Import Firmware File
</a-button>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
</div>
<div class="flex-row">
<div class="ml5">
<a-select
style="width: 150px"
v-model:value="param.firmware_status"
@select="getAllFirmwares(pageParam)">
<a-select-option
v-for="(key, value) in FirmwareStatusEnum"
:key="key"
:value="value"
>
{{ key }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-select
style="width: 150px"
v-model:value="param.device_name"
@select="getAllFirmwares(pageParam)">
<a-select-option
v-for="item in deviceNameList"
:key="item.label"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-input-search
:enter-button="true"
v-model:value="param.product_version"
placeholder="input search verison"
style="width: 250px"
@search="getAllFirmwares(pageParam)"/>
</div>
</div>
</div>
<div class="table flex-display flex-column">
<a-table :columns="columns" :data-source="data.firmware" :pagination="paginationProp" @change="refreshData" row-key="firmware_id"
:rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
<template #device_name="{ record }">
<div v-for="text in record.device_name" :key="text">
{{ text }}
</div>
</template>
<template #file_size="{ record }">
<div>{{ bytesToSize(record.file_size) }}</div>
</template>
<template #firmware_status="{ record }">
<DeviceFirmwareStatus :firmware="record" />
</template>
<template v-for="col in ['file_name', 'release_note']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
import { message, notification, PaginationProps } from 'ant-design-vue'
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'
import { IPage } from '/@/api/http/type'
import { getFirmwares, importFirmareFile } from '/@/api/manage'
import DeviceFirmwareStatus from '/@/components/devices/DeviceFirmwareStatus.vue'
import { ELocalStorageKey } from '/@/types'
import { UploadOutlined } from '@ant-design/icons-vue'
import { Firmware, FirmwareQueryParam, FirmwareStatusEnum, DeviceNameEnum, FirmwareUploadParam } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
import { bytesToSize } from '/@/utils/bytes'
import moment from 'moment'
interface FirmwareData {
firmware: Firmware[]
}
const columns = [
{ title: 'Model', dataIndex: 'device_name', width: 120, ellipsis: true, className: 'titleStyle', slots: { customRender: 'device_name' } },
{ title: 'File Name', dataIndex: 'file_name', width: 220, ellipsis: true, className: 'titleStyle', slots: { customRender: 'file_name' } },
{ title: 'Firmware Version', dataIndex: 'product_version', width: 180, className: 'titleStyle' },
{ title: 'File Size', dataIndex: 'file_size', width: 150, className: 'titleStyle', slots: { customRender: 'file_size' } },
{ title: 'Creator', dataIndex: 'username', width: 100, className: 'titleStyle' },
{ title: 'Release Date', dataIndex: 'released_time', width: 160, sorter: (a: Firmware, b: Firmware) => a.released_time.localeCompare(b.released_time), className: 'titleStyle' },
{ title: 'Release Note', dataIndex: 'release_note', width: 300, ellipsis: true, className: 'titleStyle', slots: { customRender: 'release_note' } },
{ title: 'Status', dataIndex: 'firmware_status', width: 100, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
]
const data = reactive<FirmwareData>({
firmware: []
})
const paginationProp = reactive({
pageSizeOptions: ['20', '50', '100'],
showQuickJumper: true,
showSizeChanger: true,
pageSize: 50,
current: 1,
total: 0
})
const deviceNameList = ref<any[]>([{ label: 'All', value: '' }])
type Pagination = TableState['pagination']
const pageParam: IPage = {
page: 1,
total: 0,
page_size: 50
}
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const param = reactive<FirmwareQueryParam>({
product_version: '',
device_name: '',
firmware_status: FirmwareStatusEnum.NONE
})
onMounted(() => {
getAllFirmwares(pageParam)
for (const key in DeviceNameEnum) {
const value = DeviceNameEnum[key]
deviceNameList.value.push({ label: value, value: value })
}
})
function refreshData (page: Pagination) {
pageParam.page = page?.current!
pageParam.page_size = page?.pageSize!
getAllFirmwares(pageParam)
}
function getAllFirmwares (page: IPage) {
getFirmwares(workspaceId, page, param).then(res => {
const firmwareList: Firmware[] = res.data.list
data.firmware = firmwareList
paginationProp.total = res.data.pagination.total
paginationProp.current = res.data.pagination.page
})
}
const sVisible = ref(false)
const uploadParam = reactive<FirmwareUploadParam>({
device_name: [],
release_note: '',
status: true
})
const rules = {
status: [{ required: true }],
release_note: [{ required: true, message: 'Please input release note.' }],
device_name: [{ required: true, message: 'Please select which models this firmware belongs to.' }]
}
interface FileItem {
uid: string;
name?: string;
status?: string;
response?: string;
url?: string;
}
interface FileInfo {
file: FileItem;
fileList: FileItem[];
}
const fileList = ref<FileItem[]>([])
function beforeUpload (file: FileItem) {
if (!file.name || !file.name?.endsWith('.zip')) {
message.error('Format error. Please select zip file.')
return false
}
fileList.value = [file]
return false
}
const formRef = ref()
function removeFile (file: FileItem) {
fileList.value = []
}
function onCancel () {
formRef.value.resetFields()
fileList.value = []
sVisible.value = false
}
const uploadFile = async () => {
if (fileList.value.length === 0) {
message.error('Please select at least one file.')
}
const uploading: string = 'uploading'
formRef.value.validate().then(async () => {
const file: FileItem = fileList.value[0]
const fileData = new FormData()
fileData.append('file', file as any, file.name)
Object.keys(uploadParam).forEach((key) => {
const val = uploadParam[key as keyof FirmwareUploadParam]
if (val instanceof Array) {
val.forEach((value) => {
fileData.append(key, value)
})
} else {
fileData.append(key, val.toString())
}
})
notification.open({
key: uploading,
message: `Uploading ${moment().format()}`,
description: `[${file.name}] is uploading... `,
duration: null
})
importFirmareFile(workspaceId, fileData).then((res) => {
if (res.code === 0) {
notification.success({
message: `Uploaded ${moment().format()}`,
description: `[${file.name}] file uploaded successfully.`,
duration: null
})
getAllFirmwares(pageParam)
} else {
notification.error({
message: `Failed to upload [${file.name}]. Check and try again.`,
description: `Error message: ${res.message} ${moment().format()}`,
style: { color: commonColor.FAIL },
duration: null,
})
}
}).finally(() => {
notification.close(uploading)
})
fileList.value = []
formRef.value.resetFields()
sVisible.value = false
})
}
</script>
<style>
.table {
background-color: white;
margin: 20px;
padding: 20px;
height: 88vh;
}
.table-striped {
background-color: #f7f9fa;
}
.ant-table {
border-top: 1px solid rgb(0,0,0,0.06);
border-bottom: 1px solid rgb(0,0,0,0.06);
}
.ant-table-tbody tr td {
border: 0;
}
.ant-table td {
white-space: nowrap;
}
.ant-table-thead tr th {
background: white !important;
border: 0;
}
th.ant-table-selection-column {
background-color: white !important;
}
.ant-table-header {
background-color: white !important;
}
</style>