Merge branch 'master' into release
This commit is contained in:
		
						commit
						97cb15bdb1
					
				
							
								
								
									
										47
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								app.js
									
									
									
									
									
								
							| @ -9,6 +9,7 @@ const config = require('./config'); | |||||||
| const so = require('./includes/storeObject'); | const so = require('./includes/storeObject'); | ||||||
| const exec = require('child_process').exec; | const exec = require('child_process').exec; | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
|  | const archiver = require('archiver'); | ||||||
| 
 | 
 | ||||||
| const app = express(); | const app = express(); | ||||||
| 
 | 
 | ||||||
| @ -63,22 +64,54 @@ app.get('/camevent', (req, res) => { | |||||||
|         res.send({ ip }); |         res.send({ ip }); | ||||||
|     }); |     }); | ||||||
| }) | }) | ||||||
| app.get('/viewcamimg/:dir/:img', async(req, res) => { | app.get(['/viewcamimg/:dir/:file', '/dlcamvideo/:dir/:file'], async(req, res) => { | ||||||
|     let dir = req.params.dir; |     let dir = req.params.dir; | ||||||
|     let img = req.params.img; |     let file = req.params.file; | ||||||
|     if (!dir || !img) return res.status(404).end(); |     if (!dir || !file) return res.sendStatus(404); | ||||||
|     try { |     try { | ||||||
|         let stat = await new Promise((resolve, reject) => { |         let stat = await new Promise((resolve, reject) => { | ||||||
|             fs.stat(path.resolve(config.cmdpath.ipcamsave, dir, img), (err, stats) => { |             fs.stat(path.resolve(config.cmdpath.ipcamsave, dir, file), (err, stats) => { | ||||||
|                 if (err) return reject(err); |                 if (err) return reject(err); | ||||||
|                 return resolve(stats); |                 return resolve(stats); | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
|         if (!stat.isFile()) return res.status(404).end(); |         if (!stat.isFile()) return res.sendStatus(404); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         return res.status(404).end(); |         return res.sendStatus(404); | ||||||
|     } |     } | ||||||
|     res.sendfile(path.resolve(config.cmdpath.ipcamsave, dir, img)) |     res.sendfile(path.resolve(config.cmdpath.ipcamsave, dir, file)) | ||||||
|  | }) | ||||||
|  | app.get('/dlevent/:dir', async(req, res) => { | ||||||
|  |     let dir = req.params.dir; | ||||||
|  |     if (!dir) return res.sendStatus(404); | ||||||
|  |     let list = []; | ||||||
|  |     try { | ||||||
|  |         let stat = await new Promise((resolve, reject) => { | ||||||
|  |             fs.stat(path.resolve(config.cmdpath.ipcamsave, dir), (err, stats) => { | ||||||
|  |                 if (err) return reject(err); | ||||||
|  |                 return resolve(stats); | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  |         if (!stat.isDirectory()) return res.sendStatus(404); | ||||||
|  |         list = await new Promise((resolve, reject) => { | ||||||
|  |             fs.readdir(path.resolve(config.cmdpath.ipcamsave, dir), (err, fis) => { | ||||||
|  |                 if (err) return reject(err); | ||||||
|  |                 return resolve(fis); | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     } catch (e) { | ||||||
|  |         return res.sendStatus(404); | ||||||
|  |     } | ||||||
|  |     res.writeHead(200, { | ||||||
|  |         'Content-Type': 'application/zip', | ||||||
|  |         'Content-disposition': 'attachment; filename=cam_event.zip' | ||||||
|  |     }); | ||||||
|  |     let zip = archiver('zip', { store: true }); | ||||||
|  |     zip.pipe(res); | ||||||
|  |     for (let i of list) { | ||||||
|  |         zip.file(path.resolve(config.cmdpath.ipcamsave, dir, i), { name: i }) | ||||||
|  |     } | ||||||
|  |     zip.finalize(); | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| app.get('/servcmd', (req, res) => { | app.get('/servcmd', (req, res) => { | ||||||
|  | |||||||
| @ -72,6 +72,7 @@ | |||||||
|     "ERR0070": "IPCam 裝置已達上限", |     "ERR0070": "IPCam 裝置已達上限", | ||||||
|     "ERR0071": "無此裝置資料", |     "ERR0071": "無此裝置資料", | ||||||
|     "ERR0072": "目錄資訊取得失敗", |     "ERR0072": "目錄資訊取得失敗", | ||||||
|  |     "ERR0073": "目錄輸入錯誤", | ||||||
| 
 | 
 | ||||||
|     "ERR7000": "命令執行失敗", |     "ERR7000": "命令執行失敗", | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
|   "author": "", |   "author": "", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "archiver": "^1.3.0", | ||||||
|     "body-parser": "^1.16.1", |     "body-parser": "^1.16.1", | ||||||
|     "cookie-parser": "^1.4.3", |     "cookie-parser": "^1.4.3", | ||||||
|     "cors": "^2.8.1", |     "cors": "^2.8.1", | ||||||
|  | |||||||
| @ -285,6 +285,38 @@ router | |||||||
|         } |         } | ||||||
|         return n(); |         return n(); | ||||||
|     }) |     }) | ||||||
|  |     .post('/delevent', async(req, res, n) => { | ||||||
|  |         if (!config.permission.ipcam) return n('ERR9000'); | ||||||
|  |         if (!tool.checkPermission(req)) return n('ERR9000'); | ||||||
|  |         let arr = req.body; | ||||||
|  |         if (!arr.data) return n('ERR0000'); | ||||||
|  |         if (!arr.data.dir) return n('ERR0073'); | ||||||
|  | 
 | ||||||
|  |         let rp = config.cmdpath.ipcamsave; | ||||||
|  |         let dp = path.resolve(rp, arr.data.dir); | ||||||
|  |         try { | ||||||
|  |             let stat = await new Promise((resolve, reject) => { | ||||||
|  |                 fs.stat(dp, (err, stats) => { | ||||||
|  |                     if (err) return reject(err); | ||||||
|  |                     return resolve(stats); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |             if (dp.split(' ')[0] == '/') return n('ERR0073'); | ||||||
|  |             await new Promise((resolve, reject) => { | ||||||
|  |                 exec(`rm -rf ${dp}`, (err, sout, serr) => { | ||||||
|  |                     if (err) return reject(err); | ||||||
|  |                     return resolve(null); | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         } catch (e) { | ||||||
|  |             return rt.err(res, e, n, 'ERR0072'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         res.api_res = { | ||||||
|  |             record: [] | ||||||
|  |         } | ||||||
|  |         return n(); | ||||||
|  |     }) | ||||||
|     .all('*', rt.send); |     .all('*', rt.send); | ||||||
| 
 | 
 | ||||||
| module.exports = router; | module.exports = router; | ||||||
| @ -1,9 +1,9 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { Modal, Grid, List, Menu, Icon } from 'semantic-ui-react'; | import { Modal, Grid, List, Menu, Icon, Message } from 'semantic-ui-react'; | ||||||
| import {convertTime} from '../../../tools'; | import {convertTime} from '../../../tools'; | ||||||
| import ImgGrid from './EvtImgGrid'; | import ImgGrid from './EvtImgGrid'; | ||||||
| 
 | 
 | ||||||
| const EventModal = ({ i18n, open, name, list, sel, closeEventModal, changeSelectEvent,refreshEvt }) => { | const EventModal = ({ i18n, open, name, list, sel, closeEventModal, changeSelectEvent,refreshEvt, delEvent }) => { | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Modal open={open} onClose={()=>{closeEventModal()}} size="fullscreen"> |         <Modal open={open} onClose={()=>{closeEventModal()}} size="fullscreen"> | ||||||
| @ -26,8 +26,17 @@ const EventModal = ({ i18n, open, name, list, sel, closeEventModal, changeSelect | |||||||
|                                             let n = t.name; |                                             let n = t.name; | ||||||
|                                             let time = n.split('-')[1]; |                                             let time = n.split('-')[1]; | ||||||
|                                            return ( |                                            return ( | ||||||
|                                             <Menu.Item key={idx} active={sel == idx} onClick={()=>{changeSelectEvent(idx)}}> |                                             <Menu.Item key={idx} active={sel == idx} > | ||||||
|                                                 {convertTime(time, true)} |                                                 <span onClick={()=>{changeSelectEvent(idx)}}  | ||||||
|  |                                                     style={{cursor: "pointer"}}>{convertTime(time, true)}</span> | ||||||
|  |                                                 <Icon name="download" title="下載事件" style={{ | ||||||
|  |                                                         cursor: "pointer", | ||||||
|  |                                                         float: 'right' | ||||||
|  |                                                     }} onClick={()=>{window.open(`/dlevent/${t.name}`, '_blank')}}/> | ||||||
|  |                                                 <Icon name="trash" title="刪除事件" style={{ | ||||||
|  |                                                         cursor: "pointer", | ||||||
|  |                                                         float: 'right' | ||||||
|  |                                                     }} onClick={()=>{delEvent(t.name)}}/> | ||||||
|                                             </Menu.Item> |                                             </Menu.Item> | ||||||
|                                         )}) |                                         )}) | ||||||
|                                     } |                                     } | ||||||
| @ -36,6 +45,25 @@ const EventModal = ({ i18n, open, name, list, sel, closeEventModal, changeSelect | |||||||
|                         </Menu> |                         </Menu> | ||||||
|                     </Grid.Column> |                     </Grid.Column> | ||||||
|                     <Grid.Column width={12}> |                     <Grid.Column width={12}> | ||||||
|  |                         { | ||||||
|  |                             sel != -1 && list[sel].files.video.length > 0 ? ( | ||||||
|  |                                 <Message> | ||||||
|  |                                     <Message.Header>影片下載</Message.Header> | ||||||
|  |                                     <p> | ||||||
|  |                                         { | ||||||
|  |                                             list[sel].files.video.map((t, idx) => { | ||||||
|  | 
 | ||||||
|  |                                                 return ( | ||||||
|  |                                                     <div key={idx}> | ||||||
|  |                                                     <a href={`/dlcamvideo/${list[sel].name}/${t}`} target="_blank">{t}</a> | ||||||
|  |                                                     </div> | ||||||
|  |                                                 ) | ||||||
|  |                                             }) | ||||||
|  |                                         } | ||||||
|  |                                     </p> | ||||||
|  |                                 </Message> | ||||||
|  |                             ) :null | ||||||
|  |                         } | ||||||
|                         { |                         { | ||||||
|                             sel != -1 ?  |                             sel != -1 ?  | ||||||
|                             ( |                             ( | ||||||
|  | |||||||
| @ -46,10 +46,10 @@ const IPCamModal = ({ i18n, open, type, data, closeModal, submitModal }) => { | |||||||
|                         </select> |                         </select> | ||||||
|                     </Form.Field> |                     </Form.Field> | ||||||
|                     <Form.Field> |                     <Form.Field> | ||||||
|                         <Input name="maxevents" label="最大儲存事件數量(1-5)" defaultValue={data.maxevents} /> |                         <Input name="maxevents" label="最大儲存事件數量(1-10)" defaultValue={data.maxevents} /> | ||||||
|                     </Form.Field> |                     </Form.Field> | ||||||
|                     <Form.Field> |                     <Form.Field> | ||||||
|                         <Input name="maximg" label="最大儲存圖片數量(1-10)" defaultValue={data.maximg} /> |                         <Input name="maximg" label="最大儲存圖片數量(1-20)" defaultValue={data.maximg} /> | ||||||
|                     </Form.Field> |                     </Form.Field> | ||||||
|                     <Grid columns={2}> |                     <Grid columns={2}> | ||||||
|                         <Grid.Column> |                         <Grid.Column> | ||||||
|  | |||||||
| @ -77,8 +77,8 @@ class IPCamPage extends React.Component { | |||||||
|     submitModal = (type, data = {}) => { |     submitModal = (type, data = {}) => { | ||||||
|         let {toggleLoading,showDialog} = this.props; |         let {toggleLoading,showDialog} = this.props; | ||||||
|         if(type == 1 && !data.id) return showDialog('資料讀取錯誤'); |         if(type == 1 && !data.id) return showDialog('資料讀取錯誤'); | ||||||
|         if(!data.maxevents || data.maxevents < 1 || data.maxevents > 5) return showDialog('事件數量請介於1-5間'); |         if(!data.maxevents || data.maxevents < 1 || data.maxevents > 10) return showDialog('事件數量請介於1-5間'); | ||||||
|         if(!data.maximg || data.maximg < 1 || data.maximg > 10) return showDialog('圖片數量請介於1-5間'); |         if(!data.maximg || data.maximg < 1 || data.maximg > 20) return showDialog('圖片數量請介於1-5間'); | ||||||
|         if(!data.name) return showDialog('請輸入名稱'); |         if(!data.name) return showDialog('請輸入名稱'); | ||||||
|         if(!data.ip) return showDialog('請輸入IP'); |         if(!data.ip) return showDialog('請輸入IP'); | ||||||
|         if(!data.model) return showDialog('請選擇型號'); |         if(!data.model) return showDialog('請選擇型號'); | ||||||
| @ -146,6 +146,21 @@ class IPCamPage extends React.Component { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     delEvent = (dir) => { | ||||||
|  |         if(!dir) return ; | ||||||
|  |         let {callConfirm, toggleLoading, showDialog} = this.props; | ||||||
|  |         callConfirm('確定刪除這筆事件?', ()=>{ | ||||||
|  |             toggleLoading(1); | ||||||
|  |             fetch('/api/ipcam/delevent', getRequest({dir})) | ||||||
|  |                 .then(response=>response.json()) | ||||||
|  |                 .then(json => { | ||||||
|  |                     toggleLoading(0); | ||||||
|  |                     if(json.status != 1) return showDialog(json.message); | ||||||
|  |                     this.getEvents(-1); | ||||||
|  |                 }) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     render() { |     render() { | ||||||
|         let {i18n} = this.props; |         let {i18n} = this.props; | ||||||
|         return ( |         return ( | ||||||
| @ -197,7 +212,8 @@ class IPCamPage extends React.Component { | |||||||
|                     name={this.state.evt.name} |                     name={this.state.evt.name} | ||||||
|                     refreshEvt={this.getEvents} |                     refreshEvt={this.getEvents} | ||||||
|                     closeEventModal={this.closeEventModal} |                     closeEventModal={this.closeEventModal} | ||||||
|                     changeSelectEvent={this.changeSelectEvent} /> |                     changeSelectEvent={this.changeSelectEvent} | ||||||
|  |                     delEvent={this.delEvent} /> | ||||||
|             </Container> |             </Container> | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user