From a1d8e4c4c82a213b0f123892d9c6c2a13dbe9e99 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Wed, 26 May 2021 22:49:30 +0200 Subject: [PATCH] ### __WORK IN PROGRESS__ * (bluefox) Improved the GUI for enumerations --- README.md | 3 + lib/socket.js | 113 ++++++------- lib/web.js | 151 ++++++++++++------ src-rx/src/TODO.md | 5 - src-rx/src/components/Connection.js | 4 +- src-rx/src/components/Enums/EnumBlock.js | 125 +++++++++++---- src-rx/src/components/Enums/EnumEditDialog.js | 20 ++- .../components/Enums/EnumTemplateDialog.js | 2 +- src-rx/src/components/Enums/EnumsMain.js | 82 ++++++++-- .../src/components/IOFields/IconSelector.js | 18 ++- src-rx/src/components/Users/UsersList.js | 58 ++++--- src-rx/src/dialogs/GitHubInstallDialog.js | 2 +- src-rx/src/dialogs/NewsAdminDialog.js | 3 + src-rx/src/i18n/de.json | 13 +- src-rx/src/i18n/en.json | 13 +- src-rx/src/i18n/es.json | 13 +- src-rx/src/i18n/fr.json | 13 +- src-rx/src/i18n/it.json | 13 +- src-rx/src/i18n/nl.json | 13 +- src-rx/src/i18n/pl.json | 13 +- src-rx/src/i18n/pt.json | 13 +- src-rx/src/i18n/ru.json | 13 +- src-rx/src/i18n/zh-cn.json | 13 +- src-rx/src/tabs/Logs.js | 96 ++++++----- src-rx/src/tabs/Objects.js | 6 +- 25 files changed, 563 insertions(+), 255 deletions(-) diff --git a/README.md b/README.md index 1fbf96440..1472146fd 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ The icons may not be reused in other projects without proper flaticon subscripti --> ## Changelog +### __WORK IN PROGRESS__ +* (bluefox) Improved the GUI for enumerations + ### 5.1.2 (2021-05-26) * (bluefox) Admin5: Fixed logs by the changing of host diff --git a/lib/socket.js b/lib/socket.js index c71b9763a..850aebc12 100644 --- a/lib/socket.js +++ b/lib/socket.js @@ -1086,13 +1086,9 @@ function IOSocket(server, settings, adapter, objects, store) { socket.on('sendTo', function (adapterInstance, command, message, callback) { if (updateSession(socket) && checkPermissions(socket, 'sendTo', callback, command)) { - adapter.sendTo(adapterInstance, command, message, function (res) { - if (typeof callback === 'function') { - setTimeout(function () { - callback(res); - }, 0); - } - }); + adapter.sendTo(adapterInstance, command, message, res => + typeof callback === 'function' && setImmediate(() => + callback(res), 0)); } }); @@ -1104,7 +1100,7 @@ function IOSocket(server, settings, adapter, objects, store) { // delLogs, readDirAsZip, writeDirAsZip, readObjectsAsZip, writeObjectsAsZip, checkLogging, updateMultihost if (updateSession(socket) && checkPermissions(socket, protectedCommands.includes(command) ? 'cmdExec' : 'sendToHost', callback, command)) { adapter.sendToHost(host, command, message, res => - typeof callback === 'function' && setTimeout(() => callback(res), 0)); + typeof callback === 'function' && setImmediate(() => callback(res))); } }); @@ -1267,59 +1263,67 @@ function IOSocket(server, settings, adapter, objects, store) { } }); - socket.on('readLogs', function (callback) { + socket.on('readLogs', function (host, callback) { if (updateSession(socket) && checkPermissions(socket, 'readLogs', callback)) { - let result = {list: []}; - - // deliver file list - try { - const config = adapter.systemConfig; - // detect file log - if (config && config.log && config.log.transport) { - for (const transport in config.log.transport) { - if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { - let filename = config.log.transport[transport].filename || 'log/'; - const parts = filename.replace(/\\/g, '/').split('/'); - parts.pop(); - filename = parts.join('/'); - if (filename[0] !== '/' && !filename.match(/^\W:/)) { - const _filename = path.normalize(__dirname + '/../../../') + filename; - if (!fs.existsSync(_filename)) { - filename = path.normalize(__dirname + '/../../') + filename; - } else { - filename = _filename; - } - } - if (fs.existsSync(filename)) { - const files = fs.readdirSync(filename); - - for (let f = 0; f < files.length; f++) { - try { - if (!files[f].endsWith('-audit.json')) { - const stat = fs.lstatSync(filename + '/' + files[f]); - if (!stat.isDirectory()) { - result.list.push({fileName: 'log/' + transport + '/' + files[f], size: stat.size}); + let timeout = setTimeout(() => { + if (timeout) { + let result = {list: []}; + + // deliver file list + try { + const config = adapter.systemConfig; + // detect file log + if (config && config.log && config.log.transport) { + for (const transport in config.log.transport) { + if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { + let filename = config.log.transport[transport].filename || 'log/'; + const parts = filename.replace(/\\/g, '/').split('/'); + parts.pop(); + filename = parts.join('/'); + if (filename[0] !== '/' && !filename.match(/^\W:/)) { + const _filename = path.normalize(__dirname + '/../../../') + filename; + if (!fs.existsSync(_filename)) { + filename = path.normalize(__dirname + '/../../') + filename; + } else { + filename = _filename; + } + } + if (fs.existsSync(filename)) { + const files = fs.readdirSync(filename); + + for (let f = 0; f < files.length; f++) { + try { + if (!files[f].endsWith('-audit.json')) { + const stat = fs.lstatSync(filename + '/' + files[f]); + if (!stat.isDirectory()) { + result.list.push({fileName: 'log/' + transport + '/' + files[f], size: stat.size}); + } + } + } catch (e) { + // push unchecked + // result.list.push('log/' + transport + '/' + files[f]); + adapter.log.error(`Cannot check file: ${filename}/${files[f]}`); } } - } catch (e) { - // push unchecked - // result.list.push('log/' + transport + '/' + files[f]); - adapter.log.error(`Cannot check file: ${filename}/${files[f]}`); } } } + } else { + result = {error: 'no file loggers'}; } + } catch (e) { + adapter.log.error(e); + result = {error: e}; } - } else { - result = {error: 'no file loggers'}; + typeof callback === 'function' && callback(result.error, result.list); } - } catch (e) { - adapter.log.error(e); - result = {error: e}; - } - if (typeof callback === 'function') { - callback(result.error, result.list); - } + }, 500); + + adapter.sendToHost(host, 'getLogFiles', null, result => { + clearTimeout(timeout); + timeout = null; + typeof callback === 'function' && callback(result.error, result.list); + }); } }); @@ -1747,7 +1751,7 @@ function IOSocket(server, settings, adapter, objects, store) { eventsThreshold.timeActivated = 0; adapter.log.info('Subscribe on all states again'); - setTimeout(function () { + setTimeout(() => { /*if (readAll) { adapter.getForeignStates('*', function (err, res) { adapter.log.info('received all states'); @@ -1765,7 +1769,6 @@ function IOSocket(server, settings, adapter, objects, store) { Object.keys(that.subscribes.stateChange).forEach(pattern => adapter.subscribeForeignStates(pattern)); - }, 50); } } @@ -1774,7 +1777,7 @@ function IOSocket(server, settings, adapter, objects, store) { if (!eventsThreshold.active) { eventsThreshold.active = true; - setTimeout(function () { + setTimeout(() => { adapter.log.info('Unsubscribe from all states, except system\'s, because over ' + eventsThreshold.repeatSeconds + ' seconds the number of events is over ' + eventsThreshold.value + ' (in last second ' + eventsThreshold.count + ')'); eventsThreshold.timeActivated = Date.now(); diff --git a/lib/web.js b/lib/web.js index cb13fa1a6..98c9abe61 100644 --- a/lib/web.js +++ b/lib/web.js @@ -12,7 +12,7 @@ const util = require('util'); const path = require('path'); const stream = require('stream'); -let zlib = null; +let zlib = null; let session; let bodyParser; @@ -254,6 +254,26 @@ function Web(settings, adapter, onReady, options) { } } + function unzipFile(filename, data, res) { + zlib = zlib || require('zlib'); + + // extract the file + try { + const text = zlib.gunzipSync(data).toString('utf8'); + if (text.length > 2 * 1024 * 1024) { + res.header('Content-Type', 'text/plain'); + res.send(text); + } else { + res.header('Content-Type', 'text/html'); + res.send(decorateLogFile(null, text)); + } + } catch (e) { + res.header('Content-Type', 'application/gzip'); + res.send(data); + adapter.log.error(`Cannot extract file ${filename}: ${e}`); + } + } + // settings: { // "port": 8080, // "auth": false, @@ -554,72 +574,101 @@ function Web(settings, adapter, onReady, options) { // send log files server.app.get('/log/*', (req, res) => { let parts = decodeURIComponent(req.url).split('/'); - parts = parts.splice(2); - const transport = parts.shift(); - let filename = parts.join('/'); - const config = adapter.systemConfig; - // detect file log - if (config && config.log && config.log.transport) { - if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { - let logFolder; - if (config.log.transport[transport].filename) { - parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/'); - parts.pop(); - logFolder = path.normalize(parts.join('/')); + if (parts.length === 5) { + parts.shift(); + parts.shift(); + const [host, transport] = parts; + parts = parts.splice(2); + let filename = parts.join('/'); + adapter.sendToHost('system.host.' + host, 'getLogFile', {filename, transport}, result => { + if (!result || result.error) { + res.status(404).send(`File ${escapeHtml(filename)} not found`); } else { - logFolder = path.join(process.cwd(), 'log'); - } - - if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) { - const _logFolder = path.normalize(path.join(__dirname + '/../../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/'); - if (!fs.existsSync(_logFolder)) { - logFolder = path.normalize(path.join(__dirname + '/../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/'); - } else { - logFolder = _logFolder; - } - } - - filename = path.normalize(path.join(logFolder, filename).replace(/\\/g, '/')).replace(/\\/g, '/'); - - if (filename.startsWith(logFolder) && fs.existsSync(filename)) { - const stat = fs.lstatSync(filename); - // if file is archive - if (filename.toLowerCase().endsWith('.gz')) { - // try to not process to big files - if (stat.size > 1024 * 1024/* || !fs.existsSync('/dev/null')*/) { + if (result.gz) { + if (result.size > 1024 * 1024) { res.header('Content-Type', 'application/gzip'); - res.sendFile(filename); + res.send(result.data); } else { - zlib = zlib || require('zlib'); - - // extract the file try { - const text = zlib.gunzipSync(fs.readFileSync(filename)).toString('utf8'); - if (text.length > 2 * 1024 * 1024) { - res.header('Content-Type', 'text/plain'); - res.send(text); - } else { - res.header('Content-Type', 'text/html'); - res.send(decorateLogFile(null, text)); - } + unzipFile(filename, result.data, res); } catch (e) { - res.sendFile(filename); + res.header('Content-Type', 'application/gzip'); + res.send(result.data); adapter.log.error(`Cannot extract file ${filename}: ${e}`); } } - } else if (stat.size > 2 * 1024 * 1024) { + } else if (result.data === undefined || result.data === null) { + res.status(404).send(`File ${escapeHtml(filename)} not found`); + } else if (result.size > 2 * 1024 * 1024) { res.header('Content-Type', 'text/plain'); - res.sendFile(filename); + res.send(result.data); } else { res.header('Content-Type', 'text/html'); - res.send(decorateLogFile(filename)); + res.send(decorateLogFile(null, result.data)); + } + } + }); + } else { + parts = parts.splice(2); + const transport = parts.shift(); + let filename = parts.join('/'); + const config = adapter.systemConfig; + + // detect file log + if (config && config.log && config.log.transport) { + if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { + let logFolder; + if (config.log.transport[transport].filename) { + parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/'); + parts.pop(); + logFolder = path.normalize(parts.join('/')); + } else { + logFolder = path.join(process.cwd(), 'log'); } - return; + if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) { + const _logFolder = path.normalize(path.join(__dirname + '/../../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/'); + if (!fs.existsSync(_logFolder)) { + logFolder = path.normalize(path.join(__dirname + '/../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/'); + } else { + logFolder = _logFolder; + } + } + + filename = path.normalize(path.join(logFolder, filename).replace(/\\/g, '/')).replace(/\\/g, '/'); + + if (filename.startsWith(logFolder) && fs.existsSync(filename)) { + const stat = fs.lstatSync(filename); + // if file is archive + if (filename.toLowerCase().endsWith('.gz')) { + // try to not process to big files + if (stat.size > 1024 * 1024/* || !fs.existsSync('/dev/null')*/) { + res.header('Content-Type', 'application/gzip'); + res.sendFile(filename); + } else { + try { + unzipFile(filename, fs.readFileSync(filename), res); + } catch (e) { + res.header('Content-Type', 'application/gzip'); + res.sendFile(filename); + adapter.log.error(`Cannot extract file ${filename}: ${e}`); + } + } + } else if (stat.size > 2 * 1024 * 1024) { + res.header('Content-Type', 'text/plain'); + res.sendFile(filename); + } else { + res.header('Content-Type', 'text/html'); + res.send(decorateLogFile(filename)); + } + + return; + } } } + + res.status(404).send(`File ${escapeHtml(filename)} not found`); } - res.status(404).send('File ' + escapeHtml(filename) + ' not found'); }); const appOptions = {}; diff --git a/src-rx/src/TODO.md b/src-rx/src/TODO.md index 66bb376e7..77d5cbcc7 100644 --- a/src-rx/src/TODO.md +++ b/src-rx/src/TODO.md @@ -14,14 +14,9 @@ Marked with "!" must be in release candidate ## Info ## Enums -- Create enums, that not exists automatically. E.g. `enum.rooms.A.kitchen` exists, but `enum.rooms.A` not, So `enum.rooms.A` must be created. -- Expand/Collapse of one enumeration to make it narrow with showing the number of objects inside -- expand/collapse all to narrow view and back ## Adapters -- do not allow update admin in bulk updater - ## Wizard ## Discovery diff --git a/src-rx/src/components/Connection.js b/src-rx/src/components/Connection.js index 607bd997c..ae773f314 100644 --- a/src-rx/src/components/Connection.js +++ b/src-rx/src/components/Connection.js @@ -1201,7 +1201,7 @@ class Connection { * Get the log files (only for admin connection). * @returns {Promise} */ - getLogsFiles() { + getLogsFiles(host) { if (Connection.isWeb()) { return Promise.reject('Allowed only in admin'); } @@ -1209,7 +1209,7 @@ class Connection { return Promise.reject(NOT_CONNECTED); } return new Promise((resolve, reject) => - this._socket.emit('readLogs', (err, files) => + this._socket.emit('readLogs', host, (err, files) => err ? reject(err) : resolve(files))); } diff --git a/src-rx/src/components/Enums/EnumBlock.js b/src-rx/src/components/Enums/EnumBlock.js index 906e06dc8..9f6644f6d 100644 --- a/src-rx/src/components/Enums/EnumBlock.js +++ b/src-rx/src/components/Enums/EnumBlock.js @@ -21,6 +21,8 @@ import FileCopyIcon from '@material-ui/icons/FileCopy'; import DownIcon from '@material-ui/icons/KeyboardArrowDown'; import UpIcon from '@material-ui/icons/KeyboardArrowUp'; import AddIcon from '@material-ui/icons/Add'; +import { FaRegFolder as IconCollapsed } from 'react-icons/fa'; +import { FaRegFolderOpen as IconExpanded } from 'react-icons/fa'; import IconChannel from '@iobroker/adapter-react/icons/IconChannel'; import IconDevice from '@iobroker/adapter-react/icons/IconDevice'; @@ -31,11 +33,10 @@ import Utils from '@iobroker/adapter-react/Components/Utils'; const boxShadowHover = '0 1px 1px 0 rgba(0, 0, 0, .4),0 6px 6px 0 rgba(0, 0, 0, .2)'; const styles = theme => ({ - enumGroupCard2: { + enumGroupCard: { border: '1px solid #FFF', borderColor: theme.palette.divider, margin: 10, - minHeight: 140, backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, transition: 'all 200ms ease-out', @@ -46,7 +47,11 @@ const styles = theme => ({ '&:hover': { overflowY: 'auto', boxShadow: boxShadowHover - } + }, + minHeight: 70, + }, + enumGroupCardExpanded:{ + minHeight: 140, }, enumUpdating: { opacity: 0.5, @@ -82,13 +87,16 @@ const styles = theme => ({ backgroundPosition: 'center', display: 'inline-block' }, + enumGroupName: { + marginLeft: 5, + }, enumGroupEnumName: { fontWeight: 900, - padding: 5 + //padding: '0 0 0 5px' }, enumGroupEnumID: { opacity: 0.7, - padding: 5, + marginLeft: 5, fontSize: 12, fontStyle: 'italic' }, @@ -96,7 +104,8 @@ const styles = theme => ({ fontSize: 12, fontWeight: 700, //marginLeft: 30, - opacity: 0.7 + opacity: 0.7, + marginTop: -4, }, enumGroupMember: { display: 'inline-flex', @@ -115,6 +124,47 @@ const styles = theme => ({ whiteSpace: 'nowrap', opacity: 0.5, }, + context: { + paddingTop: theme.spacing(1), + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), + paddingBottom: '0 !important', + }, + folderDiv: { + display: 'inline-block', + position: 'relative', + cursor: 'pointer', + }, + folder: { + width: 48, + height: 48, + }, + folderIcon: { + position: 'absolute', + top: 18, + left: 16, + width: 18, + height: 18, + zIndex: 2, + }, + folderIconExpanded: { + transform: 'skew(147deg, 183deg) scale(0.5) translate(6px, 7px)', + }, + bottomButtons: { + position: 'absolute', + right: 0, + bottom: 0 + }, + membersNumber: { + top: 40, + left: 26, + fontSize: 18, + position: 'absolute', + }, + memberNumberFolder: { + top: 46, + left: 26, + } }); class EnumBlock extends Component { @@ -216,9 +266,20 @@ class EnumBlock extends Component { style.backgroundColor = props.enum.common.color; } + const icon = props.enum?.common?.icon ? + : + ; + return
@@ -249,18 +310,16 @@ class EnumBlock extends Component {
- - - { - props.enum?.common?.icon ? - - : - - } -
+ + + {props.children ?
props.toggleEnum(props.id)}> + {props.closed ? [, icon] : [, icon]} +
: icon} +
{props.getName(props.enum?.common?.name) || props.id.split('.').pop()} @@ -274,7 +333,7 @@ class EnumBlock extends Component {
- {props.enum?.common?.members ? props.enum.common.members.map((memberId, i) => { + {!props.collapsed && props.enum?.common?.members ? props.enum.common.members.map((memberId, i) => { let member = props.members[memberId]; if (!member) { return null; @@ -315,11 +374,11 @@ class EnumBlock extends Component { - }) : null} + }) : (props.enum?.common?.members?.length ?
{props.enum?.common?.members?.length}
: '')}
- +
{ @@ -334,18 +393,12 @@ class EnumBlock extends Component { - {props.hasChildren ? - props.toggleEnum(props.id)}> - - {props.closed ? - - : - - } - - - : null} - + props.onCollapse(props.id)}> + + {props.collapsed ? : } + + +
; } } @@ -412,9 +465,10 @@ EnumBlockDrag.propTypes = { showEnumDeleteDialog: PropTypes.func, copyEnum: PropTypes.func, getName: PropTypes.func, - hasChildren: PropTypes.bool, closed: PropTypes.bool, + collapsed: PropTypes.bool, toggleEnum: PropTypes.func, + onCollapse: PropTypes.func, showEnumTemplateDialog: PropTypes.func, currentCategory: PropTypes.string, t: PropTypes.func, @@ -422,6 +476,7 @@ EnumBlockDrag.propTypes = { socket: PropTypes.object, updating: PropTypes.bool, id: PropTypes.string, + children: PropTypes.number, }; export default EnumBlockDrag; diff --git a/src-rx/src/components/Enums/EnumEditDialog.js b/src-rx/src/components/Enums/EnumEditDialog.js index f9a9b0286..adaefb9af 100644 --- a/src-rx/src/components/Enums/EnumEditDialog.js +++ b/src-rx/src/components/Enums/EnumEditDialog.js @@ -80,7 +80,7 @@ function EnumEditDialog(props) { }; const name2Id = name => - name.replace(Utils.FORBIDDEN_CHARS, '_').replace(/\s/g, '_').replace(/\./g, '_').toLowerCase(); + name.replace(Utils.FORBIDDEN_CHARS, '_').replace(/\s/g, '_').replace(/\./g, '_'); const changeShortId = (_id, short) => { let idArray = _id.split('.'); @@ -90,17 +90,27 @@ function EnumEditDialog(props) { let ICONS; if (props.enum._id.startsWith('enum.functions.')) { - ICONS = devices; + ICONS = JSON.parse(JSON.stringify(devices)); ICONS.forEach(item => { if (!item.icon.startsWith('/')) { - item.icon = require(`../../assets/devices/${item.icon}`).default + try { + item.icon = require(`../../assets/devices/${item.icon}`).default; + } catch (e) { + console.warn('Cannot load ' + item.icon); + item.icon = null; + } } }); } else if (props.enum._id.startsWith('enum.rooms.')) { - ICONS = rooms; + ICONS = JSON.parse(JSON.stringify(rooms)); ICONS.forEach(item => { if (!item.icon.startsWith('/')) { - item.icon = require(`../../assets/rooms/${item.icon}`).default + try { + item.icon = require(`../../assets/rooms/${item.icon}`).default; + } catch (e) { + console.warn('Cannot load ' + item.icon); + item.icon = null; + } } }); } diff --git a/src-rx/src/components/Enums/EnumTemplateDialog.js b/src-rx/src/components/Enums/EnumTemplateDialog.js index ec9017e9d..730634f1d 100644 --- a/src-rx/src/components/Enums/EnumTemplateDialog.js +++ b/src-rx/src/components/Enums/EnumTemplateDialog.js @@ -173,7 +173,7 @@ class EnumTemplateDialog extends Component { }} startIcon={} > - {this.props.t('Custom group')} + {this.props.prefix === 'enum.rooms' ? this.props.t('Custom room') : (this.props.prefix === 'enum.functions' ? this.props.t('Custom function') : this.props.t('Custom enumeration'))} diff --git a/src-rx/src/components/Enums/EnumsMain.js b/src-rx/src/components/Enums/EnumsMain.js index 1b85281d9..228714b44 100644 --- a/src-rx/src/components/Enums/EnumsMain.js +++ b/src-rx/src/components/Enums/EnumsMain.js @@ -24,14 +24,14 @@ import Popover from '@material-ui/core/Popover'; import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; import IconButton from '@material-ui/core/IconButton'; - import AddIcon from '@material-ui/icons/Add'; import { FaRegFolder as IconCollapsed } from 'react-icons/fa'; import { FaRegFolderOpen as IconExpanded } from 'react-icons/fa'; +import DownIcon from '@material-ui/icons/KeyboardArrowDown'; +import UpIcon from '@material-ui/icons/KeyboardArrowUp'; import {withStyles} from '@material-ui/core/styles'; - const styles = theme => ({ mainGridCont: { height: '100%', @@ -145,6 +145,12 @@ class EnumsList extends Component { enumsClosed = window.localStorage.getItem('enumsClosed') ? JSON.parse(window.localStorage.getItem('enumsClosed')) : {}; } catch (e) { + } + let enumsCollapsed = []; + try { + enumsCollapsed = window.localStorage.getItem('enumsCollapsed') ? JSON.parse(window.localStorage.getItem('enumsCollapsed')) : []; + } catch (e) { + } this.state = { @@ -161,7 +167,8 @@ class EnumsList extends Component { categoryPopoverOpen: false, enumPopoverOpen: false, enumsClosed, - updating: [] + updating: [], + enumsCollapsed, }; this.fastUpdate = false; @@ -428,6 +435,7 @@ class EnumsList extends Component { { + const enumsCollapsed = [...this.state.enumsCollapsed]; + const pos = enumsCollapsed.indexOf(container.id); + if (pos === -1) { + enumsCollapsed.push(container.id); + } else { + enumsCollapsed.splice(pos, 1); + } + this.setState({enumsCollapsed}); + window.localStorage.setItem('enumsCollapsed', JSON.stringify(enumsCollapsed)); + }} t={this.props.t} socket={this.props.socket} @@ -482,19 +501,23 @@ class EnumsList extends Component { await this.props.socket.setObject(enumItem._id, enumItem); if (originalId && originalId !== this.state.enumEditDialog._id) { - await this.props.socket.delObject(originalId); - - const ids = Object.keys(this.state.enums); - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - if (id.startsWith(originalId + '.')) { - let newEnumChild = JSON.parse(JSON.stringify(this.state.enums[id])); - newEnumChild._id = newEnumChild._id.replace(originalId + '.', enumItem._id + '.'); - - !updating.includes(id) && updating.push(id); - await this.props.socket.setObject(newEnumChild._id, newEnumChild); - await this.props.socket.delObject(id); + try { + await this.props.socket.delObject(originalId); + + const ids = Object.keys(this.state.enums); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (id.startsWith(originalId + '.')) { + let newEnumChild = JSON.parse(JSON.stringify(this.state.enums[id])); + newEnumChild._id = newEnumChild._id.replace(originalId + '.', enumItem._id + '.'); + + !updating.includes(id) && updating.push(id); + await this.props.socket.setObject(newEnumChild._id, newEnumChild); + await this.props.socket.delObject(id); + } } + } catch (e) { + window.alert('Cannot save enum: ' + e); } } @@ -556,6 +579,7 @@ class EnumsList extends Component { return !list.find(item => item._id === (`${prefix}.${word.toLowerCase()}_${i}`)); } + static findNewUniqueName(prefix, list, word) { let i = 1; while (!EnumsList._isUniqueName(prefix, list, word, i)) { @@ -642,6 +666,32 @@ class EnumsList extends Component { className={this.props.classes.filter} onChange={e => this.setState({search: e.target.value})} /> + + { + let enumsCollapsed = Object.keys(this.state.enums); + this.setState({enumsCollapsed}); + window.localStorage.setItem('enumsCollapsed', JSON.stringify(enumsCollapsed)); + }} + > + + + + + { + let enumsCollapsed = []; + this.setState({enumsCollapsed}); + window.localStorage.setItem('enumsCollapsed', JSON.stringify(enumsCollapsed)); + }} + > + + + + icons[i] = icon); + } } else { - return Utils.getSvg(href) - .then(icon => - icons[i] = icon); + return Promise.resolve(); } }) : []; @@ -129,7 +133,7 @@ IconSelector.propTypes = { icons: PropTypes.array, onSelect: PropTypes.func.isRequired, t: PropTypes.func.isRequired, - lang: PropTypes.func.isRequired, + lang: PropTypes.string.isRequired, }; export default IconSelector; \ No newline at end of file diff --git a/src-rx/src/components/Users/UsersList.js b/src-rx/src/components/Users/UsersList.js index 55b35837e..37bdf3c7a 100644 --- a/src-rx/src/components/Users/UsersList.js +++ b/src-rx/src/components/Users/UsersList.js @@ -320,14 +320,16 @@ class UsersList extends Component { } updateData = () => { - this.props.socket.getForeignObjects('system.user.*', 'user').then(users => { - users = Object.values(users).sort((o1, o2) => o1._id > o2._id ? 1 : -1); - - this.props.socket.getForeignObjects('system.group.*', 'group').then(groups => { + let users; + return this.props.socket.getForeignObjects('system.user.*', 'user') + .then(_users => { + users = Object.values(users).sort((o1, o2) => o1._id > o2._id ? 1 : -1); + return this.props.socket.getForeignObjects('system.group.*', 'group'); + }) + .then(groups => { groups = Object.values(groups).sort((o1, o2) => o1._id > o2._id ? 1 : -1); this.setState({groups, users}); }); - }); } changeUserFormData = user => @@ -352,8 +354,8 @@ class UsersList extends Component { this.props.socket.setObject(user._id, user) .then(() => { if (originalId && originalId !== this.state.userEditDialog._id) { - return this.props.socket.delObject(originalId).then(() => { - return Promise.all(this.state.groups.map(group => { + return this.props.socket.delObject(originalId) + .then(() => Promise.all(this.state.groups.map(group => { if (group.common.members.includes(originalId)) { let groupChanged = JSON.parse(JSON.stringify(group)); groupChanged.common.members[groupChanged.common.members.indexOf(originalId)] = user._id; @@ -361,24 +363,27 @@ class UsersList extends Component { } else { return Promise.resolve(null); } - })); - }) + }))) + .catch(e => window.alert('Cannot delete user: ' + e)); } }) .then(() => { if (newPassword) { - return this.props.socket.changePassword(user._id, newPassword); + return this.props.socket.changePassword(user._id, newPassword) + .catch(e => window.alert('Cannot change password: ' + e)); } }) .then(() => - this.setState({userEditDialog: false}, () => this.updateData())); + this.setState({userEditDialog: false}, () => + this.updateData())); } saveGroup = originalId => { this.props.socket.setObject(this.state.groupEditDialog._id, this.state.groupEditDialog) .then(() => { if (originalId && originalId !== this.state.groupEditDialog._id) { - return this.props.socket.delObject(originalId); + return this.props.socket.delObject(originalId) + .catch(e => window.alert('Cannot delete user: ' + e)); } }) .then(() => @@ -392,8 +397,8 @@ class UsersList extends Component { this.setState({groupDeleteDialog: group}); deleteUser = userId => { - this.props.socket.delObject(userId).then(() => { - return Promise.all(this.state.groups.map(group => { + this.props.socket.delObject(userId) + .then(() => Promise.all(this.state.groups.map(group => { if (group.common.members.includes(userId)) { let groupChanged = JSON.parse(JSON.stringify(group)); groupChanged.common.members.splice(groupChanged.common.members.indexOf(userId), 1); @@ -401,26 +406,29 @@ class UsersList extends Component { } else { return Promise.resolve(null); } - })); - }).then(() => { - this.setState({userDeleteDialog: false}, () => - this.updateData()); - }); + }))) + .catch(e => window.alert('Cannot delete user: ' + e)) + .then(() => { + this.setState({userDeleteDialog: false}, () => + this.updateData()); + }); }; deleteGroup = groupId => - this.props.socket.delObject(groupId).then(() => { - this.setState({groupDeleteDialog: false}, () => - this.updateData()); - }); + this.props.socket.delObject(groupId) + .then(() => this.setState({groupDeleteDialog: false}, () => + this.updateData())) + .catch(e => window.alert('Cannot delete user: ' + e)); addUserToGroup = (userId, groupId) => { let group = this.state.groups.find(group => group._id === groupId); let members = group.common.members; if (!members.includes(userId)) { members.push(userId); - this.props.socket.setObject(group._id, group).then(() => - this.updateData()); + this.props.socket.setObject(group._id, group) + .then(() => + this.updateData()) + .catch(e => window.alert('Cannot delete user: ' + e)); } }; diff --git a/src-rx/src/dialogs/GitHubInstallDialog.js b/src-rx/src/dialogs/GitHubInstallDialog.js index 5093ef807..da0ea7276 100644 --- a/src-rx/src/dialogs/GitHubInstallDialog.js +++ b/src-rx/src/dialogs/GitHubInstallDialog.js @@ -219,7 +219,7 @@ const GitHubInstallDialog = ({ categories, repository, onClose, open, installFro getOptionDisabled={option => option.nogit} renderOption={option =>
{option.name} - {option.nogit &&
{I18n.t("This adapter cannot be installed from git as must be built before installation.")}
} + {option.nogit &&
{I18n.t('This adapter cannot be installed from git as must be built before installation.')}
}
} onChange={(_, e) => setAutocompleteValue(e)} options={array()} diff --git a/src-rx/src/dialogs/NewsAdminDialog.js b/src-rx/src/dialogs/NewsAdminDialog.js index 2d9992223..bebc5e53b 100644 --- a/src-rx/src/dialogs/NewsAdminDialog.js +++ b/src-rx/src/dialogs/NewsAdminDialog.js @@ -251,6 +251,9 @@ export const checkMessages = function (messages, lastMessageId, context) { const today = Date.now(); for (let m = 0; m < messages.length; m++) { const message = messages[m]; + if (!message) { + continue; + } if (message.id === lastMessageId) { break; } diff --git a/src-rx/src/i18n/de.json b/src-rx/src/i18n/de.json index 79fba864f..35c78be99 100644 --- a/src-rx/src/i18n/de.json +++ b/src-rx/src/i18n/de.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Langsame Verbindung erkannt!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Scheint, dass Sie eine langsame Verbindung haben. Möchten Sie das Timeout-Intervall verlängern?", "Read timeout": "Lese-Timeout", - "in seconds": "in Sekunden" + "in seconds": "in Sekunden", + "Do you want to delete enum \"%s\"?": "Möchten Sie die Aufzählung \"%s\" löschen?", + "Custom room": "Benutzerdefinierter Raum", + "Create new room": "Neuen Raum schaffen", + "Custom function": "Benutzerdefinierte Funktion", + "Create new function": "Neue Funktion erstellen", + "Custom enumeration": "Benutzerdefinierte Aufzählung", + "This adapter cannot be installed from git as must be built before installation.": "Dieser Adapter kann nicht über Git installiert werden, da er vor der Installation gebaut werden muss.", + "Show members": "Mitglieder anzeigen", + "Hide members": "Mitglieder verstecken", + "Narrow all": "Alles eng machen", + "Wide all": "Weit alle" } \ No newline at end of file diff --git a/src-rx/src/i18n/en.json b/src-rx/src/i18n/en.json index 431744fd4..e71d4383e 100644 --- a/src-rx/src/i18n/en.json +++ b/src-rx/src/i18n/en.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Detected slow connection!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Seems that you have slow connection. Do you want to increase timeout interval?", "Read timeout": "Read timeout", - "in seconds": "in seconds" + "in seconds": "in seconds", + "Do you want to delete enum \"%s\"?": "Do you want to delete enum \"%s\"?", + "Custom room": "Custom room", + "Create new room": "Create new room", + "Custom function": "Custom function", + "Create new function": "Create new function", + "Custom enumeration": "Custom enumeration", + "This adapter cannot be installed from git as must be built before installation.": "This adapter cannot be installed from git as must be built before installation.", + "Show members": "Show members", + "Hide members": "Hide members", + "Narrow all": "Narrow all", + "Wide all": "Wide all" } \ No newline at end of file diff --git a/src-rx/src/i18n/es.json b/src-rx/src/i18n/es.json index 9e85633e8..3ef7b8ff6 100644 --- a/src-rx/src/i18n/es.json +++ b/src-rx/src/i18n/es.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "¡Conexión lenta detectada!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Parece que tienes una conexión lenta. ¿Quiere aumentar el intervalo de tiempo de espera?", "Read timeout": "Leer tiempo de espera", - "in seconds": "en segundos" + "in seconds": "en segundos", + "Do you want to delete enum \"%s\"?": "¿Quieres eliminar la enumeración \"%s\"?", + "Custom room": "Habitación personalizada", + "Create new room": "Crear nueva habitación", + "Custom function": "Función personalizada", + "Create new function": "Crear nueva función", + "Custom enumeration": "Enumeración personalizada", + "This adapter cannot be installed from git as must be built before installation.": "Este adaptador no se puede instalar desde git ya que se debe compilar antes de la instalación.", + "Show members": "Mostrar miembros", + "Hide members": "Ocultar miembros", + "Narrow all": "Reducir todo", + "Wide all": "Ancho todo" } \ No newline at end of file diff --git a/src-rx/src/i18n/fr.json b/src-rx/src/i18n/fr.json index d13449fef..0c9847e4b 100644 --- a/src-rx/src/i18n/fr.json +++ b/src-rx/src/i18n/fr.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Connexion lente détectée!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Il semble que vous ayez une connexion lente. Voulez-vous augmenter l'intervalle de temporisation?", "Read timeout": "Délai de lecture", - "in seconds": "en secondes" + "in seconds": "en secondes", + "Do you want to delete enum \"%s\"?": "Voulez-vous supprimer l'énumération \"%s\"?", + "Custom room": "Chambre sur mesure", + "Create new room": "Créer une nouvelle salle", + "Custom function": "Fonction personnalisée", + "Create new function": "Créer une nouvelle fonction", + "Custom enumeration": "Énumération personnalisée", + "This adapter cannot be installed from git as must be built before installation.": "Cet adaptateur ne peut pas être installé à partir de git car il doit être construit avant l'installation.", + "Show members": "Afficher les membres", + "Hide members": "Masquer les membres", + "Narrow all": "Tout restreindre", + "Wide all": "Large tout" } \ No newline at end of file diff --git a/src-rx/src/i18n/it.json b/src-rx/src/i18n/it.json index ec02bcf1b..2606747bc 100644 --- a/src-rx/src/i18n/it.json +++ b/src-rx/src/i18n/it.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Rilevata connessione lenta!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Sembra che tu abbia una connessione lenta. Vuoi aumentare l'intervallo di timeout?", "Read timeout": "Leggi timeout", - "in seconds": "in secondi" + "in seconds": "in secondi", + "Do you want to delete enum \"%s\"?": "Vuoi eliminare l'enumerazione \"%s\"?", + "Custom room": "Camera personalizzata", + "Create new room": "Crea una nuova stanza", + "Custom function": "Funzione personalizzata", + "Create new function": "Crea nuova funzione", + "Custom enumeration": "Enumerazione personalizzata", + "This adapter cannot be installed from git as must be built before installation.": "Questo adattatore non può essere installato da git poiché deve essere compilato prima dell'installazione.", + "Show members": "Mostra membri", + "Hide members": "Nascondi membri", + "Narrow all": "Restringi tutto", + "Wide all": "Ampio tutto" } \ No newline at end of file diff --git a/src-rx/src/i18n/nl.json b/src-rx/src/i18n/nl.json index c73e4126c..b8e1b0514 100644 --- a/src-rx/src/i18n/nl.json +++ b/src-rx/src/i18n/nl.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Langzame verbinding gedetecteerd!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Het lijkt erop dat u een trage verbinding heeft. Wilt u het time-outinterval verlengen?", "Read timeout": "Time-out lezen", - "in seconds": "in seconden" + "in seconds": "in seconden", + "Do you want to delete enum \"%s\"?": "Wilt u de opsomming \"%s\" verwijderen?", + "Custom room": "Aangepaste kamer", + "Create new room": "Maak een nieuwe kamer", + "Custom function": "Aangepaste functie", + "Create new function": "Maak een nieuwe functie", + "Custom enumeration": "Aangepaste opsomming", + "This adapter cannot be installed from git as must be built before installation.": "Deze adapter kan niet vanuit git worden geïnstalleerd, aangezien deze vóór de installatie moet worden gebouwd.", + "Show members": "Toon leden", + "Hide members": "Verberg leden", + "Narrow all": "Alles smal", + "Wide all": "Breed allemaal" } \ No newline at end of file diff --git a/src-rx/src/i18n/pl.json b/src-rx/src/i18n/pl.json index c0b012373..80f48492d 100644 --- a/src-rx/src/i18n/pl.json +++ b/src-rx/src/i18n/pl.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Wykryto wolne połączenie!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Wygląda na to, że masz wolne połączenie. Czy chcesz wydłużyć limit czasu?", "Read timeout": "Przeczytaj limit czasu", - "in seconds": "w sekundy" + "in seconds": "w sekundy", + "Do you want to delete enum \"%s\"?": "Czy chcesz usunąć wyliczenie „%s”?", + "Custom room": "Pokój niestandardowy", + "Create new room": "Utwórz nowy pokój", + "Custom function": "Funkcja niestandardowa", + "Create new function": "Utwórz nową funkcję", + "Custom enumeration": "Wyliczenie niestandardowe", + "This adapter cannot be installed from git as must be built before installation.": "Ta karta nie może być zainstalowana z git, ponieważ musi zostać zbudowana przed instalacją.", + "Show members": "Pokaż członków", + "Hide members": "Ukryj członków", + "Narrow all": "Zawęź wszystko", + "Wide all": "Szerokie wszystko" } \ No newline at end of file diff --git a/src-rx/src/i18n/pt.json b/src-rx/src/i18n/pt.json index 93760088b..4ca60ffd4 100644 --- a/src-rx/src/i18n/pt.json +++ b/src-rx/src/i18n/pt.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "Detectada conexão lenta!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Parece que sua conexão está lenta. Você deseja aumentar o intervalo de tempo limite?", "Read timeout": "Tempo de ler esgotado", - "in seconds": "em segundos" + "in seconds": "em segundos", + "Do you want to delete enum \"%s\"?": "Quer excluir o enum \"%s\"?", + "Custom room": "Sala personalizada", + "Create new room": "Criar uma nova sala", + "Custom function": "Função personalizada", + "Create new function": "Criar nova função", + "Custom enumeration": "Enumeração personalizada", + "This adapter cannot be installed from git as must be built before installation.": "Este adaptador não pode ser instalado a partir do git, pois deve ser construído antes da instalação.", + "Show members": "Mostrar membros", + "Hide members": "Ocultar membros", + "Narrow all": "Limitar tudo", + "Wide all": "Tudo amplo" } \ No newline at end of file diff --git a/src-rx/src/i18n/ru.json b/src-rx/src/i18n/ru.json index 7e57567c2..8491ed4ae 100644 --- a/src-rx/src/i18n/ru.json +++ b/src-rx/src/i18n/ru.json @@ -1401,5 +1401,16 @@ "Detected slow connection!": "Обнаружено медленное соединение!", "Seems that you have slow connection. Do you want to increase timeout interval?": "Похоже, у вас медленное соединение. Вы хотите увеличить интервал тайм-аута?", "Read timeout": "Таймаут чтения", - "in seconds": "в секундах" + "in seconds": "в секундах", + "Do you want to delete enum \"%s\"?": "Вы хотите удалить перечисление \"%s\"?", + "Custom room": "Пользовательская комната", + "Create new room": "Создать новую комнату", + "Custom function": "Пользовательская функция", + "Create new function": "Создать новую функцию", + "Custom enumeration": "Пользовательское перечисление", + "This adapter cannot be installed from git as must be built before installation.": "Этот адаптер нельзя установить из git, так как он должен быть собран перед установкой.", + "Show members": "Показать участников", + "Hide members": "Скрыть участников", + "Narrow all": "Сузить все", + "Wide all": "Расширить все" } \ No newline at end of file diff --git a/src-rx/src/i18n/zh-cn.json b/src-rx/src/i18n/zh-cn.json index 1c922e4b4..61e17d810 100644 --- a/src-rx/src/i18n/zh-cn.json +++ b/src-rx/src/i18n/zh-cn.json @@ -1397,5 +1397,16 @@ "Detected slow connection!": "检测到连接速度慢!", "Seems that you have slow connection. Do you want to increase timeout interval?": "似乎您的连接速度很慢。您是否要增加超时间隔?", "Read timeout": "读取超时", - "in seconds": "马上" + "in seconds": "马上", + "Do you want to delete enum \"%s\"?": "您要删除枚举“ %s”吗?", + "Custom room": "定制房间", + "Create new room": "建立新房间", + "Custom function": "自定义功能", + "Create new function": "建立新功能", + "Custom enumeration": "自定义枚举", + "This adapter cannot be installed from git as must be built before installation.": "无法从git安装此适配器,因为必须在安装前先进行构建。", + "Show members": "显示成员", + "Hide members": "隐藏会员", + "Narrow all": "全部缩小", + "Wide all": "全部宽" } \ No newline at end of file diff --git a/src-rx/src/tabs/Logs.js b/src-rx/src/tabs/Logs.js index 67f866c23..b348c3e93 100644 --- a/src-rx/src/tabs/Logs.js +++ b/src-rx/src/tabs/Logs.js @@ -334,7 +334,7 @@ class Logs extends Component { static getDerivedStateFromProps(props, state) { if (props.currentHost !== state.currentHost) { //this.ignoreNextLogs = true; - return{currentHost: props.currentHost, logs: []}; + return{currentHost: props.currentHost, logs: [], logFiles: null}; } else { return null; } @@ -404,6 +404,47 @@ class Logs extends Component { } } + readLogFiles() { + return this.props.socket.getLogsFiles(this.state.currentHost) + .then(list => { + if (list && list.length) { + const logFiles = []; + + list.reverse(); + // first 2018-01-01 + list.forEach(file => { + const parts = file.fileName.split('/'); + const name = parts.pop().replace(/iobroker\.?/, '').replace('.log', ''); + + if (name[0] <= '9') { + logFiles.push({ + path: file, + name: name + }); + } + }); + + // then restart.log ans so on + list.sort(); + list.forEach(file => { + const parts = file.fileName.split('/'); + const name = parts.pop().replace(/iobroker\.?/, '').replace('.log', ''); + + if (name[0] > '9') { + logFiles.push({ + path: file, + name + }); + } + }); + + return logFiles; + } else { + return []; + } + }); + } + componentDidMount() { this.props.logsWorker && this.props.logsWorker.enableCountErrors(false); this.props.logsWorker.registerHandler(this.logHandler); @@ -417,45 +458,10 @@ class Logs extends Component { _hosts.forEach(item => hosts[item._id] = item); this.setState({ adapters, hosts }, () => - this.props.socket.getLogsFiles() - .then(list => resolve(list))); + resolve()); })) - .then(list => { - if (list && list.length) { - const logFiles = []; - - list.reverse(); - // first 2018-01-01 - list.forEach(file => { - const parts = file.fileName.split('/'); - const name = parts.pop().replace(/iobroker\.?/, '').replace('.log', ''); - - if (name[0] <= '9') { - logFiles.push({ - path: file, - name: name - }); - } - }); - - // then restart.log ans so on - list.sort(); - list.forEach(file => { - const parts = file.fileName.split('/'); - const name = parts.pop().replace(/iobroker\.?/, '').replace('.log', ''); - - if (name[0] > '9') { - logFiles.push({ - path: file, - name - }); - } - }); - - this.readLogs(true, logFiles); - } - })); - + .then(() => this.readLogFiles()) + .then(logFiles => this.readLogs(true, logFiles))); } componentWillUnmount() { @@ -766,6 +772,16 @@ class Logs extends Component { } const { classes } = this.props; + if (this.state.logFiles === null && !this.readLogsInProcess) { + this.readLogsInProcess = true; + setTimeout(() => + this.readLogFiles() + .then(logFiles => { + this.readLogsInProcess = false; + this.setState({logFiles}); + }), 100); + } + const pauseChild = !this.state.pause ? : {this.state.logs.length - this.state.pause}; @@ -842,7 +858,7 @@ class Logs extends Component {
- {this.state.logFiles.length > 0 && + {this.state.logFiles?.length > 0 &&