| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- <template>
- <div class="onvif-container">
- <div class="settings-section">
- <!-- Title -->
- <div class="section-header">
- <h3>ONVIF Protocol Configuration</h3>
- </div>
- <!-- ONVIF Settings Form -->
- <el-form ref="formRef" :model="onvifConfig" :rules="formRules" label-width="140px" class="onvif-form">
- <!-- Enable ONVIF -->
- <el-form-item label="Enable ONVIF">
- <el-switch v-model="onvifConfig.OnvifEnable"/>
- </el-form-item>
- <template v-if="onvifConfig.OnvifEnable">
- <!-- IP Address -->
- <el-form-item label="IP Address" prop="IpAddr">
- <el-input v-model="onvifConfig.IpAddr" placeholder="Enter IP address"/>
- </el-form-item>
- <!-- Port -->
- <el-form-item label="Port" prop="Port">
- <el-input v-model="onvifConfig.Port" placeholder="Enter port number"/>
- </el-form-item>
- <!-- ONVIF Username -->
- <el-form-item label="ONVIF Username" prop="OnvifName">
- <el-input v-model="onvifConfig.OnvifName" placeholder="Enter ONVIF username"/>
- </el-form-item>
- <!-- ONVIF Password -->
- <el-form-item label="ONVIF Password" prop="Password">
- <el-input v-model="onvifConfig.Password" type="password" placeholder="Enter ONVIF password" show-password/>
- </el-form-item>
- <!-- Save Button -->
- <el-form-item>
- <el-button type="primary" @click="saveOnvifConfig" :loading="saving">Save</el-button>
- </el-form-item>
- </template>
- </el-form>
- <!-- Access Example -->
- <!-- <div class="examples-section" v-if="onvifConfig.OnvifEnable">-->
- <!-- <h4>ONVIF Device Manager Access Example</h4>-->
- <!-- <div class="example-box">-->
- <!-- <div class="example-title">Using ONVIF Device Manager</div>-->
- <!-- <div class="code-block">-->
- <!-- <code>{{ onvifAccessUrl }}</code>-->
- <!-- <el-button type="primary" @click="copyToClipboard(onvifAccessUrl)">-->
- <!-- Copy-->
- <!-- </el-button>-->
- <!-- </div>-->
- <!-- <div class="tip-text">-->
- <!-- Use the above URL with your ONVIF Device Manager software to connect to the camera.-->
- <!-- </div>-->
- <!-- </div>-->
- <!-- </div>-->
- </div>
- <!-- Save Success Toast -->
- <Transition name="toast">
- <div v-if="showSaveToast" class="toast">
- ✓ Configuration saved successfully
- </div>
- </Transition>
- <!-- Copy Success Toast -->
- <Transition name="toast">
- <div v-if="showCopyToast" class="toast">
- ✓ Copied to clipboard
- </div>
- </Transition>
- </div>
- </template>
- <script setup lang="ts">
- import {ref, computed, onMounted} from 'vue'
- import {getOnvifProtocolApi, OnvifProtocolApi} from '@/api/protocol'
- import {ElMessage} from 'element-plus'
- import type {FormInstance, FormRules} from 'element-plus'
- const formRef = ref<FormInstance>()
- const onvifConfig = ref({
- OnvifEnable: false,
- IpAddr: '',
- Port: '',
- OnvifName: '',
- Password: ''
- })
- const saving = ref(false)
- const showSaveToast = ref(false)
- const showCopyToast = ref(false)
- // IP address validation
- const validateIp = (_rule: any, value: string, callback: any) => {
- if (!value) return callback(new Error('Please enter IP address'))
- const ipReg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/
- if (!ipReg.test(value)) {
- callback(new Error('Please enter a valid IP address'))
- } else {
- callback()
- }
- }
- // Port validation
- const validatePort = (_rule: any, value: string, callback: any) => {
- if (!value) return callback(new Error('Please enter port number'))
- const port = Number(value)
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
- callback(new Error('Port range is 1-65535'))
- } else {
- callback()
- }
- }
- // Dynamically generate validation rules based on ONVIF switch
- const formRules = computed<FormRules>(() => {
- if (!onvifConfig.value.OnvifEnable) return {}
- return {
- IpAddr: [
- {required: true, validator: validateIp, trigger: 'blur'}
- ],
- Port: [
- {required: true, validator: validatePort, trigger: 'blur'}
- ],
- OnvifName: [
- {required: true, message: 'Please enter username', trigger: 'blur'},
- {min: 6, max: 16, message: 'Username must be 6-16 characters', trigger: 'blur'}
- ],
- Password: [
- {required: true, message: 'Please enter password', trigger: 'blur'},
- {min: 6, max: 16, message: 'Password must be 6-16 characters', trigger: 'blur'}
- ]
- }
- })
- // Computed ONVIF access URL
- // const onvifAccessUrl = computed(() => {
- // return `http://${onvifConfig.value.IpAddr}:${onvifConfig.value.Port}/onvif/device_service`
- // })
- onMounted(async () => {
- try {
- const resp = await getOnvifProtocolApi()
- if (resp.data) {
- onvifConfig.value = {
- OnvifEnable: resp.data.OnvifEnable,
- IpAddr: resp.data.IpAddr,
- Port: resp.data.Port,
- OnvifName: resp.data.OnvifName,
- Password: resp.data.Password
- }
- }
- } catch (error) {
- console.error('Failed to fetch ONVIF configuration:', error)
- }
- })
- const saveOnvifConfig = async () => {
- if (!formRef.value) return
- await formRef.value.validate(async (valid) => {
- if (!valid) return
- saving.value = true
- try {
- const response = await OnvifProtocolApi(onvifConfig.value)
- if ((response as any).data === 'ok\n') {
- ElMessage.success('Configuration saved successfully')
- } else {
- ElMessage.error('Failed to save configuration')
- }
- } catch (error) {
- ElMessage.error('Failed to save configuration')
- } finally {
- saving.value = false
- }
- })
- }
- // // Copy to clipboard
- // const copyToClipboard = async (text: string) => {
- // try {
- // await navigator.clipboard.writeText(text)
- // showCopySuccess()
- // } catch (error) {
- // console.error('Failed to copy:', error)
- // }
- // }
- //
- // // Show copy success toast
- // const showCopySuccess = () => {
- // showCopyToast.value = true
- // setTimeout(() => {
- // showCopyToast.value = false
- // }, 2000)
- // }
- </script>
- <style lang="scss" scoped>
- .onvif-container {
- max-width: 800px;
- .settings-section {
- background: #ffffff;
- border-radius: 8px;
- padding: 24px;
- border: 1px solid #e0e0e0;
- .section-header {
- margin-bottom: 24px;
- padding-bottom: 12px;
- border-bottom: 1px solid #f0f0f0;
- h3 {
- font-size: 18px;
- font-weight: 600;
- color: #1f2937;
- margin: 0;
- letter-spacing: -0.3px;
- }
- }
- }
- }
- // ONVIF Form
- .onvif-form {
- margin-bottom: 28px;
- .el-form-item {
- margin-bottom: 16px;
- }
- .el-button {
- margin-top: 8px;
- }
- }
- // Examples Section
- .examples-section {
- h4 {
- font-size: 14px;
- font-weight: 600;
- color: #1f2937;
- margin: 0 0 14px 0;
- letter-spacing: -0.2px;
- }
- .example-box {
- background: #f9fafb;
- border: 1px solid #e5e7eb;
- border-radius: 6px;
- padding: 18px;
- margin-bottom: 14px;
- transition: all 0.3s ease;
- &:hover {
- background: #ffffff;
- border-color: #d1d5db;
- }
- .example-title {
- font-size: 13px;
- font-weight: 600;
- color: #1f2937;
- margin-bottom: 14px;
- letter-spacing: -0.2px;
- }
- .code-block {
- background: #ffffff;
- border: 1px solid #e5e7eb;
- border-radius: 6px;
- padding: 14px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 12px;
- margin-bottom: 12px;
- code {
- font-size: 12px;
- color: #0ea5e9;
- font-family: 'Monaco', 'Courier New', 'Menlo', monospace;
- flex: 1;
- word-break: break-all;
- letter-spacing: 0.2px;
- }
- }
- .tip-text {
- font-size: 12px;
- color: #4b5563;
- line-height: 1.7;
- letter-spacing: 0.2px;
- code {
- background: #f0f1f3;
- padding: 3px 7px;
- border-radius: 3px;
- font-family: 'Monaco', 'Courier New', 'Menlo', monospace;
- font-size: 11px;
- color: #0284c7;
- }
- }
- }
- }
- // Toast Notification
- .toast {
- position: fixed;
- bottom: 24px;
- right: 24px;
- background-color: rgba(38, 38, 38, 0.95);
- color: white;
- padding: 12px 20px;
- border-radius: 6px;
- font-size: 13px;
- font-weight: 500;
- z-index: 1000;
- box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
- animation: slideInUp 0.3s ease;
- }
- @keyframes slideInUp {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
- .toast-enter-active,
- .toast-leave-active {
- transition: opacity 0.3s ease, transform 0.3s ease;
- }
- .toast-enter-from,
- .toast-leave-to {
- opacity: 0;
- transform: translateY(20px);
- }
- </style>
|