index.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <div class="onvif-container">
  3. <div class="settings-section">
  4. <!-- Title -->
  5. <div class="section-header">
  6. <h3>ONVIF Protocol Configuration</h3>
  7. </div>
  8. <!-- ONVIF Settings Form -->
  9. <el-form ref="formRef" :model="onvifConfig" :rules="formRules" label-width="140px" class="onvif-form">
  10. <!-- Enable ONVIF -->
  11. <el-form-item label="Enable ONVIF">
  12. <el-switch v-model="onvifConfig.OnvifEnable"/>
  13. </el-form-item>
  14. <template v-if="onvifConfig.OnvifEnable">
  15. <!-- IP Address -->
  16. <el-form-item label="IP Address" prop="IpAddr">
  17. <el-input v-model="onvifConfig.IpAddr" placeholder="Enter IP address"/>
  18. </el-form-item>
  19. <!-- Port -->
  20. <el-form-item label="Port" prop="Port">
  21. <el-input v-model="onvifConfig.Port" placeholder="Enter port number"/>
  22. </el-form-item>
  23. <!-- ONVIF Username -->
  24. <el-form-item label="ONVIF Username" prop="OnvifName">
  25. <el-input v-model="onvifConfig.OnvifName" placeholder="Enter ONVIF username"/>
  26. </el-form-item>
  27. <!-- ONVIF Password -->
  28. <el-form-item label="ONVIF Password" prop="Password">
  29. <el-input v-model="onvifConfig.Password" type="password" placeholder="Enter ONVIF password" show-password/>
  30. </el-form-item>
  31. <!-- Save Button -->
  32. <el-form-item>
  33. <el-button type="primary" @click="saveOnvifConfig" :loading="saving">Save</el-button>
  34. </el-form-item>
  35. </template>
  36. </el-form>
  37. <!-- Access Example -->
  38. <!-- <div class="examples-section" v-if="onvifConfig.OnvifEnable">-->
  39. <!-- <h4>ONVIF Device Manager Access Example</h4>-->
  40. <!-- <div class="example-box">-->
  41. <!-- <div class="example-title">Using ONVIF Device Manager</div>-->
  42. <!-- <div class="code-block">-->
  43. <!-- <code>{{ onvifAccessUrl }}</code>-->
  44. <!-- <el-button type="primary" @click="copyToClipboard(onvifAccessUrl)">-->
  45. <!-- Copy-->
  46. <!-- </el-button>-->
  47. <!-- </div>-->
  48. <!-- <div class="tip-text">-->
  49. <!-- Use the above URL with your ONVIF Device Manager software to connect to the camera.-->
  50. <!-- </div>-->
  51. <!-- </div>-->
  52. <!-- </div>-->
  53. </div>
  54. <!-- Save Success Toast -->
  55. <Transition name="toast">
  56. <div v-if="showSaveToast" class="toast">
  57. ✓ Configuration saved successfully
  58. </div>
  59. </Transition>
  60. <!-- Copy Success Toast -->
  61. <Transition name="toast">
  62. <div v-if="showCopyToast" class="toast">
  63. ✓ Copied to clipboard
  64. </div>
  65. </Transition>
  66. </div>
  67. </template>
  68. <script setup lang="ts">
  69. import {ref, computed, onMounted} from 'vue'
  70. import {getOnvifProtocolApi, OnvifProtocolApi} from '@/api/protocol'
  71. import {ElMessage} from 'element-plus'
  72. import type {FormInstance, FormRules} from 'element-plus'
  73. const formRef = ref<FormInstance>()
  74. const onvifConfig = ref({
  75. OnvifEnable: false,
  76. IpAddr: '',
  77. Port: '',
  78. OnvifName: '',
  79. Password: ''
  80. })
  81. const saving = ref(false)
  82. const showSaveToast = ref(false)
  83. const showCopyToast = ref(false)
  84. // IP address validation
  85. const validateIp = (_rule: any, value: string, callback: any) => {
  86. if (!value) return callback(new Error('Please enter IP address'))
  87. const ipReg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/
  88. if (!ipReg.test(value)) {
  89. callback(new Error('Please enter a valid IP address'))
  90. } else {
  91. callback()
  92. }
  93. }
  94. // Port validation
  95. const validatePort = (_rule: any, value: string, callback: any) => {
  96. if (!value) return callback(new Error('Please enter port number'))
  97. const port = Number(value)
  98. if (!Number.isInteger(port) || port < 1 || port > 65535) {
  99. callback(new Error('Port range is 1-65535'))
  100. } else {
  101. callback()
  102. }
  103. }
  104. // Dynamically generate validation rules based on ONVIF switch
  105. const formRules = computed<FormRules>(() => {
  106. if (!onvifConfig.value.OnvifEnable) return {}
  107. return {
  108. IpAddr: [
  109. {required: true, validator: validateIp, trigger: 'blur'}
  110. ],
  111. Port: [
  112. {required: true, validator: validatePort, trigger: 'blur'}
  113. ],
  114. OnvifName: [
  115. {required: true, message: 'Please enter username', trigger: 'blur'},
  116. {min: 6, max: 16, message: 'Username must be 6-16 characters', trigger: 'blur'}
  117. ],
  118. Password: [
  119. {required: true, message: 'Please enter password', trigger: 'blur'},
  120. {min: 6, max: 16, message: 'Password must be 6-16 characters', trigger: 'blur'}
  121. ]
  122. }
  123. })
  124. // Computed ONVIF access URL
  125. // const onvifAccessUrl = computed(() => {
  126. // return `http://${onvifConfig.value.IpAddr}:${onvifConfig.value.Port}/onvif/device_service`
  127. // })
  128. onMounted(async () => {
  129. try {
  130. const resp = await getOnvifProtocolApi()
  131. if (resp.data) {
  132. onvifConfig.value = {
  133. OnvifEnable: resp.data.OnvifEnable,
  134. IpAddr: resp.data.IpAddr,
  135. Port: resp.data.Port,
  136. OnvifName: resp.data.OnvifName,
  137. Password: resp.data.Password
  138. }
  139. }
  140. } catch (error) {
  141. console.error('Failed to fetch ONVIF configuration:', error)
  142. }
  143. })
  144. const saveOnvifConfig = async () => {
  145. if (!formRef.value) return
  146. await formRef.value.validate(async (valid) => {
  147. if (!valid) return
  148. saving.value = true
  149. try {
  150. const response = await OnvifProtocolApi(onvifConfig.value)
  151. if ((response as any).data === 'ok\n') {
  152. ElMessage.success('Configuration saved successfully')
  153. } else {
  154. ElMessage.error('Failed to save configuration')
  155. }
  156. } catch (error) {
  157. ElMessage.error('Failed to save configuration')
  158. } finally {
  159. saving.value = false
  160. }
  161. })
  162. }
  163. // // Copy to clipboard
  164. // const copyToClipboard = async (text: string) => {
  165. // try {
  166. // await navigator.clipboard.writeText(text)
  167. // showCopySuccess()
  168. // } catch (error) {
  169. // console.error('Failed to copy:', error)
  170. // }
  171. // }
  172. //
  173. // // Show copy success toast
  174. // const showCopySuccess = () => {
  175. // showCopyToast.value = true
  176. // setTimeout(() => {
  177. // showCopyToast.value = false
  178. // }, 2000)
  179. // }
  180. </script>
  181. <style lang="scss" scoped>
  182. .onvif-container {
  183. max-width: 800px;
  184. .settings-section {
  185. background: #ffffff;
  186. border-radius: 8px;
  187. padding: 24px;
  188. border: 1px solid #e0e0e0;
  189. .section-header {
  190. margin-bottom: 24px;
  191. padding-bottom: 12px;
  192. border-bottom: 1px solid #f0f0f0;
  193. h3 {
  194. font-size: 18px;
  195. font-weight: 600;
  196. color: #1f2937;
  197. margin: 0;
  198. letter-spacing: -0.3px;
  199. }
  200. }
  201. }
  202. }
  203. // ONVIF Form
  204. .onvif-form {
  205. margin-bottom: 28px;
  206. .el-form-item {
  207. margin-bottom: 16px;
  208. }
  209. .el-button {
  210. margin-top: 8px;
  211. }
  212. }
  213. // Examples Section
  214. .examples-section {
  215. h4 {
  216. font-size: 14px;
  217. font-weight: 600;
  218. color: #1f2937;
  219. margin: 0 0 14px 0;
  220. letter-spacing: -0.2px;
  221. }
  222. .example-box {
  223. background: #f9fafb;
  224. border: 1px solid #e5e7eb;
  225. border-radius: 6px;
  226. padding: 18px;
  227. margin-bottom: 14px;
  228. transition: all 0.3s ease;
  229. &:hover {
  230. background: #ffffff;
  231. border-color: #d1d5db;
  232. }
  233. .example-title {
  234. font-size: 13px;
  235. font-weight: 600;
  236. color: #1f2937;
  237. margin-bottom: 14px;
  238. letter-spacing: -0.2px;
  239. }
  240. .code-block {
  241. background: #ffffff;
  242. border: 1px solid #e5e7eb;
  243. border-radius: 6px;
  244. padding: 14px;
  245. display: flex;
  246. justify-content: space-between;
  247. align-items: center;
  248. gap: 12px;
  249. margin-bottom: 12px;
  250. code {
  251. font-size: 12px;
  252. color: #0ea5e9;
  253. font-family: 'Monaco', 'Courier New', 'Menlo', monospace;
  254. flex: 1;
  255. word-break: break-all;
  256. letter-spacing: 0.2px;
  257. }
  258. }
  259. .tip-text {
  260. font-size: 12px;
  261. color: #4b5563;
  262. line-height: 1.7;
  263. letter-spacing: 0.2px;
  264. code {
  265. background: #f0f1f3;
  266. padding: 3px 7px;
  267. border-radius: 3px;
  268. font-family: 'Monaco', 'Courier New', 'Menlo', monospace;
  269. font-size: 11px;
  270. color: #0284c7;
  271. }
  272. }
  273. }
  274. }
  275. // Toast Notification
  276. .toast {
  277. position: fixed;
  278. bottom: 24px;
  279. right: 24px;
  280. background-color: rgba(38, 38, 38, 0.95);
  281. color: white;
  282. padding: 12px 20px;
  283. border-radius: 6px;
  284. font-size: 13px;
  285. font-weight: 500;
  286. z-index: 1000;
  287. box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
  288. animation: slideInUp 0.3s ease;
  289. }
  290. @keyframes slideInUp {
  291. from {
  292. opacity: 0;
  293. transform: translateY(20px);
  294. }
  295. to {
  296. opacity: 1;
  297. transform: translateY(0);
  298. }
  299. }
  300. .toast-enter-active,
  301. .toast-leave-active {
  302. transition: opacity 0.3s ease, transform 0.3s ease;
  303. }
  304. .toast-enter-from,
  305. .toast-leave-to {
  306. opacity: 0;
  307. transform: translateY(20px);
  308. }
  309. </style>