Primer commit de los tres flujos: Raspipeso, Servidor y Gestor de semaforos

This commit is contained in:
2026-01-28 22:11:54 +01:00
parent 62d340d7e7
commit 57983096b1
72 changed files with 18931 additions and 2 deletions
+10131
View File
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -1 +1,6 @@
{} {
"1c8613764b7f685e": {
"user": "trypton",
"password": "Trypton#2022"
}
}
@@ -0,0 +1,53 @@
/**
* Template Socket.IO Connection Middleware for uibuilder.
* UPDATED: 2022-04-01
*
* NOTES & WARNINGS:
* 1) This function is only called ONCE - when a new client connects. So any authentication/security processing is limited
* because you cannot use this to, for example, timeout/extend a session without further server processing of incoming messages.
* However, see the sioUse.js and sioMsgOut.js middlewares for per-msg handling.
* 2) Failing to either return or call `next()` will mean that your clients will never connect.
* 3) An error in this function will probably cause Node-RED to fail to start at all.
* 4) You have to restart Node-RED if you change this file.
* 5) To use for authentication/authorisation with Express and sio connection middleware, create a common node.js module.
*
* Allows custom processing for authentication, session management, connection validation, logging, rate limiting, etc.
*
* see also: uibRoot/.config/sioUse.js & sioMsgOut.js
* https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#websocket-implementation-hints
*
* @param {*} socket Socket.IO socket object
* @param {function} next The callback to hand off to the next middleware
*/
function sioMw(socket, next) {
// Some SIO related info that might be useful in security checks
console.log('== [sioMiddleware.js] ====================')
console.log('New client connected to Namespace')
console.log('--socket.request.connection.remoteAddress--', socket.request.connection.remoteAddress)
console.log('--socket id--', socket.id)
// Added by uibuilder when Namespace is created by uibuilder node instance - Note also that socket.nsp.log can be used to output to the Node-RED log
console.log('--socket namespace metadata (server)--', {
name: socket.nsp.name,
url: socket.nsp.url,
nodeId: socket.nsp.nodeId,
useSecurity: socket.nsp.useSecurity,
})
//console.log('--socket handshake--', socket.handshake)
//console.log('--socket properties--', Object.keys(socket))
// Show the client id (set by uibuilder ExpressJS middleware)
console.log('--client id handshake.auth--', socket.handshake.auth.clientId)
console.log('--client id in custom header (polling only)--', socket.handshake.headers['x-clientid'])
console.log('==========================================\n ')
// Simplistic auth example
let auth = true
if (auth !== true) {
socket.nsp.log.error(`[uibuilder:sioMiddleware.js] - Authentication error, client disconnected - ID: ${socket.id}`)
return next (new Error(`[uibuilder:sioMiddleware.js] - Authentication error, client disconnected - ID: ${socket.id}` ))
}
return next()
} // Remember to end with a `next()` statement or nothing will work.
module.exports = sioMw
+22
View File
@@ -0,0 +1,22 @@
/**
* Template Socket.IO outbound per-msg middleware for uibuilder. Fn will be called for EVERY outbound msg from Node-RED/uibuilder to a client.
* UPDATED: 2022-04-01
*
* NOTES & WARNINGS:
* 1) This function is called whenever any instance of uibuilder sends a msg to any client.
* 2) You have to restart Node-RED if you change this file.
* 3) You can use this to make changes to the msg before it is sent.
*
* Allows you to process outgoing data to clients. Use it to add security/user data or anything else.
*
* @param {object} msg The msg being seny by uibuilder to a client
* @param {string} url The uibuilder instance url
* @param {string} channel The socket.io channel being used
*/
function sioMsgOutMw( msg, url, channel ) {
console.log('[uibuilder:Socket.IO:sioMsgOut.js] msg from server: ', msg, url, channel)
}
module.exports = sioMsgOutMw
+43
View File
@@ -0,0 +1,43 @@
/**
* Template Socket.IO `use` middleware for uibuilder. Fn will be called for EVERY inbound msg from a client to Node-RED/uibuilder.
* UPDATED: 2022-04-01
*
* NOTES & WARNINGS:
* 1) This function is called when a client sends a "packet" of data to the server.
* 2) Failing to either return or call `next()` will mean that your clients will never be able to get responses.
* 3) You can amend the incoming msg in this middleware.
* 4) An error in this function will probably cause Node-RED to fail to start at all.
* 5) You have to restart Node-RED if you change this file.
* 6) If you call `next( new Error('blah') )` The error is sent back to the client and further proessing of the incoming msg stops.
* 7) To use for authentication/authorisation with Express and sio connection middleware, create a common node.js module.
*
* Allows you to process incoming data from clients.
*
* see: https://socket.io/docs/v4/server-api/#socketusefn
* see also: uibRoot/.config/sioMiddleware.js & sioMsgOut.js
* and https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#websocket-implementation-hints
*
* @param {[string,Array<Object>]} data The channel name (strictly the event name) and args send by a client (Socket.IO calls it a "packet"). data[0] is the channel/event name, data[args][0] is the actual msg
* @param {function} next The callback to hand off to the next middleware
*/
function sioUseMw([ channel, ...args ], next) {
const msg = args[0]
console.log('[uibuilder:Socket.IO:sioUse.js] msg from client: ', 'Channel Name:', channel, ' Msg:', msg)
// Simplistic error example - looking for specific property on the inbound msg
if ( msg.i_am_an_error ) {
// The error is sent back to the client and further processing of the msg stops
next(new Error('Oops! Some kind of error happened'))
return
}
// You can amend the incoming msg
msg._test = 'added by sioUse.js middleware'
next()
} // Do not forget to end with a call to `next()` or clients will not be able to connect
module.exports = sioUseMw
@@ -0,0 +1,26 @@
/**
* Template ExpressJS Middleware for uibuilder.
* UPDATED: 2022-04-01
*
* NOTES & WARNINGS:
* 1) This function is called EVERY TIME any web call is made to the URL defined by your uib instance.
* So it should be kept short and efficient.
* 2) Failing to either return or call `next()` will cause an ExpressJS error.
* 3) An error in this function will probably cause Node-RED to fail to start at all.
* 4) You have to restart Node-RED if you change this file.
* 5) To use for authentication/authorisation with sio connection middleware, create a common node.js module.
*
* Allows custom processing for authentication, session management, custom logging, etc.
*
* @param {object} req The ExpressJS request object
* @param {object} res The ExpressJS result object
* @param {function} next The callback to hand off to the next middleware
*/
function uibMw(req,res,next) {
console.log('[uibuilder:uibMiddleware.js] Custom ExpressJS middleware called.')
next()
} // Do not forget to end with a call to `next()`
module.exports = uibMw
+178
View File
@@ -0,0 +1,178 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+77
View File
@@ -0,0 +1,77 @@
# uibuilder Template: Blank (Default)
> NOTE: You can replace the contents of this README with text that describes your UI.
This is about the simplest template you can get for uibuilder. Is is also (as of uibuilder v5+), the default template.
It does not use any frameworks and has no other dependencies. It demonstrates that you can use uibuilder purely with HTML/JavaScript or even just HTML and still easily build a simple, dynamic, data-driven user interface with the help of Node-RED.
All it does is load the uibuilder client library and connect to Node-RED.
## UI
Initially only shows an H1 heading with a sub-heading. However, it contains a `<div>` with the id "`more`" which is used by many of the examples in the Node-RED import library.
In addition, the `more` div uses uibuilder's `uib-topic` special attribute which allows it to be used as a target for messages sent from Node-RED. This is a useful feature that allows you to easily update the content of the page without having to write any JavaScript code. Send a message containing `{ topic: 'more', payload: 'Hello World' }` to the `uibuilder` node and the content of the `more` div will be updated with "Hello World". Note that the payload can contain HTML. As an example, use an inject node with `msg.payload` set to use a JSONata expression like `"<b style='background-color:var(--error)'>Hello!</b> This is a message from Node-RED at " & $moment()`. Don't forget to set `msg.topic` to `more` so that the uibuilder client library knows where to send the message.
> **WARNING**: Using the "more" topic completely overwrites the contents of the `more` div.
## Folders
* `/` - The root folder contains this file. It can be used for other things **but** it will not be served up in the Node-RED web server.
* `/src/` - the default folder that serves files as web resources. However, this can be changed to a different folder if desired.
* `/dist/` - the default folder for serving files as web resources where a build step is used. In that case, the `/src` folder is the source used by the build tool and `/dist` is the destination for the build (the "distribution" folder).
* `/routes/` - This folder can contain `.js` files defining routing middleware for uibuilder's ExpressJS web server.
* `/api/` - This folder can contain `.js` files defining REST API's specific to this uibuilder instance.
* `/types/` - Contains typescript definition files (`*.d.ts`) for the uibuilder client library. This is not used by uibuilder but can be used by your IDE to provide type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Remember to update these for new uibuilder versions.
The above folders will all pre-exist for the built-in uibuilder templates. The folders can safely be removed if not needed but one folder must exist to serve the web resources from (this cannot be the root folder).
The template only has files in the root and `src` folders. The `src` folder is the default used by uibuilder to serve up files to clients.
One reserved item in the root folder however will be a `package.json` file. This will be used in the future to help with build/compile steps. You can still use it yourself, just bear in mind that a future version of uibuilder will make use it as well. If you need to have any development packages installed to build your UI, don't forget to tell `npm` to save them as development dependencies not normal dependencies.
The `dist` folder should be used if you have a build step to convert your source code to something that browsers understand. So if you are using a build (compile) step to produce your production code, ensure that it is configured to use the `dist` folder as the output folder and that it creates at least an `index.html` file.
You can switch between the `src` and `dist` (or other) folders using the matching setting in the Editor. See uibuilder's advanced settings tab.
Also note that you can use **linked** folders and files in this folder structure. This can be handy if you want to maintain your code in a different folder somewhere or if your default build process needs to use sub-folders other than `src` and `dist`.(Though as of v6, you can specify any sub-folder to be served)
## Files in this template
* `package.json`: REQUIRED. Defines the basic structure, name, description of the project and defines any local development dependencies if any. Also works with `npm` allowing the installation of dev packages (such as build or linting tools).
* `README.md`: This file. Change this to describe your web app and provide documentation for it.
* `eslint.config.js`: A pre-configured configuration for the ESLINT tool. Helps when writing front-end code. Note that you need at least eslint v8+ installed for this to work.
* `LICENSE`: A copy of the Apache 2.0 license. Replace with a different license if needed. Always license your code. Apache 2.0 matches the licensing of uibuilder.
* `src/index.html`: REQUIRED. Contains your basic HTML and will be the file loaded and displayed in the browser when going to the uibuilder defined URL.
* `src/index.js`: Contains all of the logic for your UI. It must be linked to in the html file. Optional.
* `src/index.css`: Contains your custom CSS for styling. It must be linked to in the html file. Optional.
* `tsconfig.json`: A configuration file for TypeScript. This can be used by your IDE to provide descriptions, type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Uses the typescript definition files in the `/types` folder, remember to update these for new uibuilder versions.
Note that only the `package.json` and `index.html` files are actually _required_. uibuilder will not function as expected without them.
It is possible to use the index.html file simply as a link to other files but it must be present.
The other files are all optional. However, you will need to change the index.html file accordingly if you rename or remove them.
## Multiple HTML pages
uibuilder will happily serve up any number of web pages from a single instance. It will also make use of sub-folders. However, each folder should have an `index.html` file so that a URL that ends with the folder name will still work without error.
Note that each html file is a separate page and requires its own JavaScript and uibuilder library reference. When moving between pages, remember that every page is stand-alone, a new environment. You can share one `index.js` file between multiple pages if you prefer but each page will run a separate instance.
If multiple pages are connected to the same uibuilder instance, they will all get the same broadcast messages from Node-RED. So if you want to handle different messages on different pages, remember to filter them in your front-end JavaScript in `uibuilder.onChange('msg', ....)` function. Turn on the advanced flag for including a `msg._uib` property in output if you need to differentiate between pages and/or clients in Node-RED.
## URL endpoints
When specifying links in your HTML, CSS and JavaScript files, you should use relative URLs. e.g. `./index.mjs` will load that file from the `src` folder or wherever else you have told uibuilder to use.
When using uibuilder's server-side resources, you will generally use `../uibuilder/....`, for example `../uibuilder/uib-brand.min.css` as seen in the default `index.css` file. When accessing a front-end library being served by uibuilder, you can use the form `../uibuilder/vendor/....`. Use the "Full details" button in the uibuilder node to see all of the possible endpoints you may want to use.
## License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
This template may be used however you like. It is provided as a test template for uibuilder and is not intended to be a full template. You are free to use it as a starting point for your own template or to use it as-is if you find it useful.
View File
View File
+121
View File
@@ -0,0 +1,121 @@
import { defineConfig } from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'
import jsdoc from 'eslint-plugin-jsdoc'
import stylistic from '@stylistic/eslint-plugin'
import html from 'eslint-plugin-html'
// Shared rules
const jsdocRules = {
'jsdoc/check-alignment': 'off',
// "jsdoc/check-indentation": ["warn", {"excludeTags":['example', 'description']}],
'jsdoc/check-indentation': 'off',
'jsdoc/check-param-names': 'warn',
'jsdoc/check-tag-names': ['warn', {
definedTags: ['typicalname', 'element', 'memberOf', 'slot', 'csspart'],
}],
'jsdoc/multiline-blocks': ['error', {
noZeroLineText: false,
}],
'jsdoc/no-multi-asterisk': 'off',
'jsdoc/no-undefined-types': ['error', {
definedTypes: ['JQuery', 'NodeListOf', 'ProxyHandler'],
}],
'jsdoc/tag-lines': 'off',
}
const stylisticRules = {
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true, }],
'@stylistic/comma-dangle': ['error', {
arrays: 'only-multiline',
objects: 'always',
imports: 'never',
exports: 'always-multiline',
functions: 'never',
importAttributes: 'never',
dynamicImports: 'never',
}],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/indent': ['error', 4, {
SwitchCase: 1,
}],
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/linebreak-style': ['error', 'unix'],
'@stylistic/lines-between-class-members': 'off',
'@stylistic/newline-per-chained-call': ['error', {
ignoreChainWithDepth: 2,
}],
'@stylistic/no-confusing-arrow': 'error',
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/semi': ['error', 'never'],
'@stylistic/space-before-function-paren': 'off',
'@stylistic/spaced-comment': ['error', 'always', {
line: {
exceptions: ['*', '#region', '#endregion'],
},
block: {
exceptions: ['*'],
},
}],
'@stylistic/space-in-parens': 'off',
'@stylistic/quotes': ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: 'always',
}],
}
const generalRules = {
'new-cap': 'error',
'no-else-return': 'error',
'no-empty': ['error', {
allowEmptyCatch: true,
}],
'no-unused-vars': 'off',
'no-useless-escape': 'off',
'no-var': 'warn',
'prefer-const': 'error',
}
export default defineConfig([
// Apply to all JavaScript files
{
files: ['**/*.js', '**/*.html'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'script', // Use script rather than ES modules
globals: {
...globals.browser,
// ...globals.node,
UibRouter: 'readonly',
uibuilder: 'readonly',
$: 'readonly',
$$: 'readonly',
},
},
plugins: {
'js': js,
'jsdoc': jsdoc,
'@stylistic': stylistic,
'html': html,
},
extends: [
js.configs.recommended,
jsdoc.configs['flat/recommended'],
stylistic.configs.recommended,
],
rules: {
...jsdocRules,
...stylisticRules,
...generalRules,
// 'no-empty': ['error', { 'allowEmptyCatch': true }],
},
},
// Specific rules for configuration files
{
files: ['eslint.config.mjs', '**/*.config.js', '**/*.config.mjs'],
languageOptions: {
sourceType: 'module', // Config files can use ES modules
},
},
])
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uib-blank",
"version": "2025-05-27",
"private": true,
"description": "This is about the simplest template you can get for uibuilder.",
"browser": "./src/index.js",
"scripts": {
"build": "echo \"No build process specified\""
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@stylistic/eslint-plugin": "^4.2.0",
"eslint": "^9.27.0",
"eslint-plugin-html": "^8.1.3",
"eslint-plugin-jsdoc": "^50.6.17",
"globals": "^16.1.0"
},
"keywords": ["uibuilder", "node-red", "node-red-contrib-uibuilder"],
"author": "Julian Knight (Totally Information)",
"license": "Apache-2.0",
"homepage": "https://github.com/TotallyInformation/node-red-contrib-uibuilder",
"bugs": "https://github.com/TotallyInformation/node-red-contrib-uibuilder/issues",
"repository": {
"type": "git",
"url": "https://github.com/TotallyInformation/node-red-contrib-uibuilder.git"
},
"browserslist": [
"> 0.5%",
"maintained versions",
"last 2 versions",
"not dead",
"not ie > 0"
]
}
View File
+140
View File
@@ -0,0 +1,140 @@
body, html {
margin: 0;
padding: 0;
background: white;
font-family: sans-serif;
width: 100vw;
height: 100vh;
overflow: hidden;
/*cursor: none;*/
}
.pantalla-container {
width: 800px;
height: 460px;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
.grid-zonas {
display: grid;
grid-template-areas:
"peso semaforo"
"mensajes datos";
grid-template-columns: 70% 30%;
grid-template-rows: 50% 50%;
width: 100%;
height: 100%;
padding: 10px;
box-sizing: border-box;
}
.peso {
grid-area: peso;
display: flex;
align-items: center;
justify-content: center;
font-size: 80px;
font-weight: bold;
}
.peso-valor {
font-size: 160px;
text-align: center;
}
.peso-unidad {
font-size: 28px;
margin-left: 10px;
}
.semaforo {
grid-area: semaforo;
display: flex;
align-items: center;
justify-content: center;
}
#semaforo {
width: 150px;
height: 150px;
border-radius: 50%;
background-color: gray;
box-shadow: 0 0 12px rgba(0,0,0,0.3);
}
.mensajes {
grid-area: mensajes;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 6px;
padding-top: 10px;
}
.linea-mensaje {
font-size: 30px;
font-weight: bold;
padding: 4px 12px;
border-radius: 6px;
min-height: 28px;
width: 100%;
max-width: 500px;
}
.btn-ok {
font-size: 56px;
font-weight: bold;
padding: 15px 100px;
background-color: #28a745;/* valor por defecto */
color: orange;
border: none;
border-radius: 10px;
display: none;
}
#mensajes-informativos {
margin-top: 10px;
display: none;
font-size: 20px;
color: #8B4513;
}
#mensajes-informativos p {
margin: 5px 0;
}
.datos {
grid-area: datos;
display: flex;
flex-direction: column;
justify-content: center;
gap: 15px;
padding-top: 10px;
}
.dato-bloque .label-mat {
font-weight: bold;
font-size: 28px;
color: #003366;
text-align: center;
}
.dato-bloque .label-id {
font-weight: bold;
font-size: 28px;
color: #003366;
text-align: center;
}
.dato-bloque .valor {
font-size: 32px;
font-weight: bold;
color: #b30000;
text-align: center;
padding-top: 10px;
min-height: 40px;
}
/*
div {
border: 1px dashed red !important;
}
*/
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate">
<title>Pantalla Totem</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="../uibuilder/uibuilder.iife.min.js"></script>
<link rel="stylesheet" href="./index.css">
<script src="./index.js" defer></script>
</head>
<body>
<div class="pantalla-container">
<div class="grid-zonas">
<div class="peso" id="peso-container">
<span id="peso" class="peso-valor">0</span><span class="peso-unidad">Kg</span>
</div>
<div class="semaforo">
<div id="semaforo"></div>
</div>
<div class="mensajes" id="zona-dinamica">
<div id="mensaje1" class="linea-mensaje"></div>
<div id="mensaje2" class="linea-mensaje"></div>
<div id="mensaje3" class="linea-mensaje"></div>
<button id="boton-ok" class="btn-ok">OK</button>
</div>
<div class="datos">
<div class="dato-bloque">
<div class="label-mat">MATRICULA</div>
<div id="matricula" class="valor"> </div>
</div>
<div class="dato-bloque">
<div class="label-id">TARJETA ID</div>
<div id="tarjetaid" class="valor"> </div>
</div>
</div>
</div>
</div>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
uibuilder.start();
uibuilder.onChange('msg', msg => {
if (msg.hasOwnProperty("peso")) document.getElementById('peso').textContent = msg.peso;
if (msg.hasOwnProperty("id_m2")) document.getElementById('matricula').textContent = msg.id_m2
if (msg.hasOwnProperty("id_m4")) document.getElementById('tarjetaid').textContent = msg.id_m4
if (msg.hasOwnProperty("semaforo")) document.getElementById('semaforo').style.backgroundColor = msg.semaforo;
const boton = document.getElementById('boton-ok');
const m1 = document.getElementById('mensaje1');
const m2 = document.getElementById('mensaje2');
const m3 = document.getElementById('mensaje3');
const payload = msg.payload || {};
// Mensajes individuales
if (msg.hasOwnProperty("men_m1")) {
m1.textContent = msg.men_m1;
m1.style.color = msg.men_m1_color || 'black';
m1.style.display = 'block';
} else {
m1.style.display = 'none';
}
if (!(msg.button == "button3") && msg.hasOwnProperty("men_m2")) {
m2.textContent = msg.men_m2;
m2.style.color = msg.men_m2_color || 'black';
m2.style.display = 'block';
} else {
m2.style.display = 'none';
}
if (!(msg.button == "button3") && msg.hasOwnProperty("men_m3")) {
m3.textContent = msg.men_m3;
m3.style.color = msg.men_m2_color || 'black';
m3.style.display = 'block';
} else {
m3.style.display = 'none';
}
if (msg.hasOwnProperty("men_m1")) {
const m1 = document.getElementById('mensaje1');
m1.textContent = msg.men_m1;
m1.style.display = 'block';
m1.style.color = msg.men_m1_color || 'black';
} else {
document.getElementById('mensaje1').style.display = 'none';
}
// Botón
if (msg.button == "button3") {
//console.log("boton ok")
boton.style.display = 'inline-block';
boton.textContent = msg.button3 || 'OK';
boton.style.backgroundColor = msg.botonvalidacion_background || '#28a745';
boton.style.color = msg.botonvalidacion_foreground || 'orange';
}
else {
boton.style.display = 'none';
}
});
document.getElementById('boton-ok').addEventListener('click', () => {
document.getElementById('boton-ok').style.display = 'none';
document.getElementById('mensaje1').style.display = 'none';
uibuilder.send({ payload: { boton: "ok" } });
});
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"baseUrl": "./src",
"typeRoots": ["./types"]
},
"include": ["types", "src/**/*.js"]
}
+12
View File
@@ -0,0 +1,12 @@
/// <reference path="./uibuilder.module.d.ts" />
/**
* Make the uibuilder instance globally available (from uibuilder.module.d.ts)
* @version 7.3.0
*/
declare global {
// Use typeof import to reference the Uib class from the module
const uibuilder: import("./uibuilder.module").Uib;
}
export {};
+647
View File
@@ -0,0 +1,647 @@
/**
* Type definitions for uibuilder.module.js
* WCAG 2.2 AA, ESLint v9, Shift-Left security, and project conventions applied.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export type HtmlString = string
/** Column metadata for tables */
export interface ColumnDefinition {
index: number,
hasName: boolean,
title: string,
name?: string,
key?: string | number,
dataType?: 'string' | 'date' | 'number' | 'html',
editable?: boolean,
}
/** Options for building HTML tables */
export interface TableOptions {
cols?: ColumnDefinition[],
parent?: HTMLElement | string,
allowHTML?: boolean,
}
/** Options for tblAddListener */
export interface TableListenerOptions {
eventScope?: 'row' | 'cell',
returnType?: 'text' | 'html',
pad?: number,
send?: boolean,
logLevel?: string | number,
eventType?: string,
}
/** Options for notification */
export interface NotificationConfig {
title?: string,
body?: string,
return?: boolean,
[key: string]: any,
}
/**
* Uibuilder main class
* @typicalname uibuilder
* @description The client-side Front-End JavaScript for uibuilder in HTML Module form.
* Provides a number of global objects that can be used in your own JavaScript.
* See the docs folder `./docs/uibuilder.module.md` for details of how to use this fully.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export class Uib {
/**
* Static metadata for the Uibuilder client
*/
static _meta: {
version: string,
type: string,
displayName: string,
}
/** Client ID set by uibuilder on connect */
clientId: string
/** The collection of cookies provided by uibuilder */
cookies: Record<string, string>
/** Copy of last control msg object received from server */
ctrlMsg: object
/** Is Socket.IO client connected to the server? */
ioConnected: boolean
/** Is the library running from a minified version? */
isMinified: boolean
/** Is the browser tab containing this page visible or not? */
isVisible: boolean
/** Remember the last page (re)load/navigation type: navigate, reload, back_forward, prerender */
lastNavType: string
/** Max msg size that can be sent over Socket.IO - updated by "client connect" msg receipt */
maxHttpBufferSize: number
/** Last std msg received from Node-RED */
msg: object
/** Number of messages sent to server since page load */
msgsSent: number
/** Number of messages received from server since page load */
msgsReceived: number
/** Number of control messages sent to server since page load */
msgsSentCtrl: number
/** Number of control messages received from server since page load */
msgsCtrlReceived: number
/** Is the client online or offline? */
online: boolean
/** Last control msg object sent via uibuilder.send() */
sentCtrlMsg: object
/** Last std msg object sent via uibuilder.send() */
sentMsg: object
/** Placeholder to track time offset from server, see fn socket.on(ioChannels.server ...) */
serverTimeOffset: number | null
/** Placeholder for a socket error message */
socketError: string | null
/** Tab identifier from session storage */
tabId: string
/** Actual name of current page (set in constructor) */
pageName: string | null
/** Is the DOMPurify library loaded? Updated in start() */
purify: boolean
/** Is the Markdown-IT library loaded? Updated in start() */
markdown: boolean
/** Current URL hash. Initial set is done from start->watchHashChanges via a set to make it watched */
urlHash: string
/** Default originator node id - empty string by default */
originator: string
/** Optional default topic to be included in outgoing standard messages */
topic?: string
/** Either undefined or a reference to a uib router instance. Set by uibrouter, do not set manually. */
uibrouterinstance?: any
/** Set by uibrouter, do not set manually */
uibrouter_CurrentRoute?: any
/** Internal: auto-send ready flag */
autoSendReady: boolean
/** Node-RED setting (via cookie) */
httpNodeRoot: string
/** Socket.IO namespace - unique to each uibuilder node instance */
ioNamespace: string
/** Socket.IO path */
ioPath: string
/** Starting delay factor for subsequent reconnect attempts */
retryFactor: number
/** Starting retry ms period for manual socket reconnections workaround */
retryMs: number
/** Prefix for all uib-related localStorage */
storePrefix: string
/** Whether uibuilder client has started */
started: boolean
/** Socket.IO connection options */
socketOptions: object
// --- Getters/Setters ---
logLevel: number
meta: typeof Uib._meta
/**
* Set uibuilder properties to a new value - works on any property except _* or #*
* Also triggers any event listeners.
* @param prop Any uibuilder property who's name does not start with a _ or #
* @param val The set value of the property or a string declaring that a protected property cannot be changed
* @param store If true, the variable is also saved to the browser localStorage if possible
* @param autoload If true & store is true, on load, uib will try to restore the value from the store automatically
* @returns Input value
*/
set(prop: string, val: any, store?: boolean, autoload?: boolean): any
/**
* Get the value of a uibuilder property
* @param prop The name of the property to get as long as it does not start with a _ or #
* @returns The current value of the property
*/
get(prop: string): any
/**
* Write to localStorage if possible. Console error output if can't write
* Also uses this.storePrefix
* @param id localStorage var name to be used (prefixed with 'uib_')
* @param value value to write to localstore
* @param autoload If true, on load, uib will try to restore the value from the store
* @returns True if succeeded else false
*/
setStore(id: string, value: any, autoload?: boolean): boolean
/**
* Attempt to get and re-hydrate a key value from localStorage
* @param id The key of the value to attempt to retrieve
* @returns The re-hydrated value of the key or null if key not found, undefined on error
*/
getStore(id: string): any
/**
* Remove a given id from the uib keys in localStorage
* @param id The key to remove
*/
removeStore(id: string): void
/**
* Returns a list of uibuilder properties (variables) that can be watched with onChange
* @returns List of uibuilder managed variables
*/
getManagedVarList(): Record<string, string>
/**
* Returns a list of currently watched variables
* @returns List of watched variable names
*/
getWatchedVars(): string[]
/**
* Register on-change event listeners for uibuilder tracked properties
* @param prop The property of uibuilder that we want to monitor
* @param callback The function that will run when the property changes, parameter is the new value of the property after change
* @returns A reference to the callback to cancel
*/
onChange(prop: string, callback: (val: any) => void): number
/**
* Cancel a previously registered onChange event listener
* @param prop The property name
* @param cbRef The callback reference number
*/
cancelChange(prop: string, cbRef: number): void
/**
* Register a change callback for a specific msg.topic
* @param topic The msg.topic we want to listen for
* @param callback The function that will run when an appropriate msg is received
* @returns A reference to the callback to cancel
*/
onTopic(topic: string, callback: (msg: any) => void): number
/**
* Cancel a previously registered onTopic event listener
* @param topic The topic name
* @param cbRef The callback reference number
*/
cancelTopic(topic: string, cbRef: number): void
/**
* Returns a new array containing the intersection of the 2 input arrays
* @param a1 Array to check
* @param a2 Array to intersect
* @returns The intersection of the 2 arrays (may be an empty array)
*/
arrayIntersect<T>(a1: T[], a2: T[]): T[]
/**
* Copies a uibuilder variable to the browser clipboard
* @param varToCopy The name of the uibuilder variable to copy to the clipboard
*/
copyToClipboard(varToCopy: string): void
/**
* Does the chosen CSS Selector currently exist?
* @param cssSelector Required. CSS Selector to examine for visibility
* @param msg Optional, default=true. If true also sends a message back to Node-RED
* @returns True if the element exists
*/
elementExists(cssSelector: string, msg?: boolean): boolean
/**
* Format a number using the INTL standard library
* @param value Number to format
* @param decimalPlaces Number of decimal places to include
* @param intl standard locale spec, e.g. "ja-JP" or "en-GB"
* @param opts INTL library options object
* @returns formatted number
*/
formatNumber(value: number, decimalPlaces?: number, intl?: string, opts?: object): string
/**
* Attempt to get rough size of an object
* @param obj Any serialisable object
* @returns Rough size of object in bytes or undefined
*/
getObjectSize(obj: any): number | undefined
/**
* Returns true if a uibrouter instance is loaded, otherwise returns false
* @returns true if uibrouter instance loaded else false
*/
hasUibRouter(): boolean
/**
* Only keep the URL Hash & ignoring query params
* @param url URL to extract the hash from
* @returns Just the route id
*/
keepHashFromUrl(url: string): string
/**
* Custom logging function
* @param args Arguments to log
*/
log(...args: any[]): void
/**
* Makes a null or non-object into an object. If thing is already an object.
* If not null, moves "thing" to {payload:thing}
* @param thing Thing to check
* @param property property that "thing" is moved to if not null and not an object. Default='payload'
* @returns Object
*/
makeMeAnObject(thing: any, property?: string): object
/**
* Navigate to a new page or a new route (hash)
* @param url URL to navigate to. Can be absolute or relative (to current page) or just a hash for a route change
* @returns The new window.location string
*/
navigate(url: string): Location
/**
* Convert a string attribute into a variable/constant reference
* Used to resolve data sources in attributes
* @param path The string path to resolve, must be relative to the `window` global scope
* @returns The resolved data source or null
*/
resolveDataSource(path: string): any
/**
* Fast but accurate number rounding
* @param num The number to be rounded
* @param decimalPlaces Number of DP's to round to
* @returns Rounded number
*/
round(num: number, decimalPlaces: number): number
/**
* Set the default originator. Set to '' to ignore. Used with uib-sender.
* @param originator A Node-RED node ID to return the message to
*/
setOriginator(originator?: string): void
/**
* HTTP Ping/Keep-alive - makes a call back to uibuilder's ExpressJS server and receives a 204 response
* Can be used to keep sessions alive.
* @param ms Repeat interval in ms
*/
setPing(ms?: number): void
/**
* Convert JSON to Syntax Highlighted HTML
* @param json A JSON/JavaScript Object
* @returns Object reformatted as highlighted HTML
*/
syntaxHighlight(json: object): HtmlString
/**
* Returns true/false or a default value for truthy/falsy and other values
* @param val The value to test
* @param deflt Default value to use if the value is not truthy/falsy
* @returns The truth! Or the default
*/
truthy(val: any, deflt: any): boolean | any
/**
* Joins all arguments as a URL string
* @param paths URL fragments
* @returns Joined URL string
*/
urlJoin(...paths: string[]): string
/**
* Turn on/off/toggle sending URL hash changes back to Node-RED
* @param toggle Optional on/off/etc
* @returns True if we will send a msg to Node-RED on a hash change
*/
watchUrlHash(toggle?: any): boolean
/**
* DEPRECATED FOR NOW - wasn't working properly.
* Is the chosen CSS Selector currently visible to the user? NB: Only finds the FIRST element of the selection.
* @returns False
*/
elementIsVisible(): false
// --- UI handlers ---
/**
* Simplistic jQuery-like document CSS query selector, returns an HTML Element.
* If the selected element is a <template>, returns the first child element.
* @param cssSelector A CSS Selector that identifies the element to return
* @returns Selected HTML element or null
*/
$: (cssSelector: string) => HTMLElement | null
/**
* CSS query selector that returns ALL found selections as an array of elements.
* @param cssSelector A CSS Selector that identifies the elements to return
* @returns Array of DOM elements/nodes. Array is empty if selector is not found.
*/
$$: (cssSelector: string) => HTMLElement[]
/**
* Reference to the full ui library
*/
$ui: any
/**
* Add one or several class names to an element
* @param classNames Single or array of classnames
* @param el HTML Element to add class(es) to
*/
addClass(classNames: string | string[], el: HTMLElement): void
/**
* Apply a source template tag to a target html element
* @param source The source element
* @param target The target element
* @param onceOnly If true, the source will be adopted (the source is moved)
*/
applyTemplate(source: HTMLElement, target: HTMLElement, onceOnly: boolean): void
/**
* Builds an HTML table from an array (or object) of objects
* @param data Input data array or object
* @param opts Table options
* @returns Output HTML Element
*/
buildHtmlTable(data: object[] | object, opts?: TableOptions): HTMLTableElement | HTMLParagraphElement
/**
* Directly add a table to a parent element.
* @param data Input data array or object
* @param opts Build options
*/
createTable(data?: object[] | any[], opts?: TableOptions): void
/**
* Converts markdown text input to HTML if the Markdown-IT library is loaded
* Otherwise simply returns the text
* @param mdText The input markdown string
* @returns HTML (if Markdown-IT library loaded and parse successful) or original text
*/
convertMarkdown(mdText: string): string
/**
* ASYNC: Include HTML fragment, img, video, text, json, form data, pdf or anything else from an external file or API
* @param url The URL of the source file to include
* @param uiOptions Object containing properties recognised by the _uiReplace function. Must at least contain an id
*/
include(url: string, uiOptions: object): Promise<void>
/**
* Attach a new remote script to the end of HEAD synchronously
* @param url The url to be used in the script src attribute
*/
loadScriptSrc(url: string): void
/**
* Attach a new remote stylesheet link to the end of HEAD synchronously
* @param url The url to be used in the style link href attribute
*/
loadStyleSrc(url: string): void
/**
* Attach a new text script to the end of HEAD synchronously
* @param textFn The text to be loaded as a script
*/
loadScriptTxt(textFn: string): void
/**
* Attach a new text stylesheet to the end of HEAD synchronously
* @param textFn The text to be loaded as a stylesheet
*/
loadStyleTxt(textFn: string): void
/**
* Load a dynamic UI from a JSON web response
* @param url URL that will return the ui JSON
*/
loadui(url: string): void
/**
* Remove All, 1 or more class names from an element
* @param classNames Single or array of classnames. If undefined, "" or null, remove all classes
* @param el HTML Element to remove class(es) from
*/
removeClass(classNames: string | string[] | undefined | null, el: HTMLElement): void
/**
* Replace or add an HTML element's slot from text or an HTML string
* WARNING: Executes <script> tags! And will process <style> tags.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param slot The slot content we are trying to add/replace (defaults to empty string)
*/
replaceSlot(el: Element, slot: any): void
/**
* Replace or add an HTML element's slot from a Markdown string
* Only does something if the markdownit library has been loaded to window.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param component The component we are trying to add/replace
*/
replaceSlotMarkdown(el: Element, component: any): void
/**
* Sanitise HTML to make it safe - if the DOMPurify library is loaded
* Otherwise just returns that HTML as-is.
* @param html The input HTML string
* @returns The sanitised HTML or the original if DOMPurify not loaded
*/
sanitiseHTML(html: string): string
/**
* Add table event listener that returns the text or html content of either the full row or a single cell
* @param tblSelector The table CSS Selector
* @param options Additional options
* @param out A variable reference that will be updated with the output data upon a click event
*/
tblAddListener(tblSelector: string, options?: TableListenerOptions, out?: object): void
/**
* Add a row to a table element in the DOM.
* @param tbl The table element or selector to add the row to
* @param rowData The data for the new row (object or array)
* @param options Optional configuration for row creation
* @returns The created HTMLTableRowElement
*/
tblAddRow(tbl: string | HTMLTableElement, rowData: object | any[], options?: object): HTMLTableRowElement
/**
* Remove a row from a table element in the DOM.
* @param tbl The table element or selector to remove the row from
* @param rowIndex The index of the row to remove
* @param options Optional configuration for row removal
*/
tblRemoveRow(tbl: string | HTMLTableElement, rowIndex: number, options?: object): void
/**
* Show a dialog (notification or alert) in the UI.
* @param type The dialog type: 'notify' or 'alert'
* @param ui The UI configuration object for the dialog
* @param msg Optional message object to include
*/
showDialog(type: 'notify' | 'alert', ui: object, msg?: object): void
/**
* Apply a UI definition (JSON) to the current page.
* @param json The UI definition object
*/
ui(json: object): void
/**
* Get properties or values from UI elements matching a selector.
* @param cssSelector The CSS selector for the elements
* @param propName Optional property name to retrieve
* @returns Array of property values or elements
*/
uiGet(cssSelector: string, propName?: string): any[]
/**
* Enhance a DOM element with a UI component definition.
* @param el The element to enhance
* @param component The component definition or configuration
*/
uiEnhanceElement(el: any, component: any): void
// --- DOM/HTML cache ---
/**
* Clear the cached HTML content from memory or storage.
*/
clearHtmlCache(): void
/**
* Restore HTML content from the cache into the DOM.
*/
restoreHtmlFromCache(): void
/**
* Save the current HTML content to the cache for later restoration.
*/
saveHtmlCache(): void
// --- Message Handling ---
/**
* Send a standard message to Node-RED via Socket.IO.
* @param msg The message object to send
* @param originator Optional Node-RED node ID to return the message to
*/
send(msg: object, originator?: string): void
/**
* Send a message to a specific room via Socket.IO.
* @param room The room name
* @param msg The message to send
*/
sendRoom(room: string, msg: any): void
/**
* Join a Socket.IO room.
* @param room The room name to join
*/
joinRoom(room: string): void
/**
* Leave a Socket.IO room.
* @param room The room name to leave
*/
leaveRoom(room: string): void
/**
* Send a control message to Node-RED via Socket.IO.
* @param msg The control message object to send
*/
sendCtrl(msg: object): void
/**
* Send a custom message on a specific channel via Socket.IO.
* @param channel The custom channel name
* @param msg The message object to send
*/
sendCustom(channel: string, msg: object): void
/**
* Upload a file to the server via Socket.IO.
* @param file The file to upload
* @param meta Optional metadata to send with the file
*/
uploadFile(file: File, meta?: object): void
// --- Socket.IO ---
/**
* Connect the Socket.IO client to the server.
*/
connect(): void
/**
* Disconnect the Socket.IO client from the server.
*/
disconnect(): void
// --- Startup ---
/**
* Start the uibuilder client, initializing all features and connections.
* @param options Optional startup options
*/
start(options?: object): void
// --- Show/hide ---
/**
* Show or hide the message area in the UI.
* @param showHide If true, show the message area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the message area is shown, false if hidden
*/
showMsg(showHide?: boolean, parent?: string): boolean
/**
* Show or hide the status area in the UI.
* @param showHide If true, show the status area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the status area is shown, false if hidden
*/
showStatus(showHide?: boolean, parent?: string): boolean
// --- Watchers ---
/**
* Watch a DOM element for changes and optionally send updates to Node-RED.
* @param cssSelector The CSS selector to watch
* @param startStop Start, stop, or toggle the watcher
* @param send If true, send updates to Node-RED
* @param showLog If true, log watcher activity
* @returns True if watching, false otherwise
*/
uiWatch(cssSelector: string, startStop?: boolean | 'toggle', send?: boolean, showLog?: boolean): boolean
/**
* Watch the DOM for changes (e.g., for dynamic UI updates).
* @param startStop Start or stop watching
*/
watchDom(startStop: boolean): void
// --- Notifications ---
/**
* Show a notification or alert in the UI.
* @param config Notification configuration or string message
* @returns A promise resolving to the notification event, or null
*/
notify(config: NotificationConfig | string): Promise<Event> | null
// --- Clipboard ---
/**
* Copy a uibuilder variable's value to the clipboard.
* @param varToCopy The name of the uibuilder variable to copy
*/
copyToClipboard(varToCopy: string): void
}
/** The default uibuilder instance */
declare const uibuilder: Uib
export { uibuilder }
export default uibuilder
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"version": "7.4.3",
"name": "uib_root",
"description": "Root configuration and data folder for uibuilder",
"scripts": {},
"homepage": "",
"bugs": "",
"author": "",
"license": "Apache-2.0",
"repository": "",
"uibuilder": {
"packages": {}
},
"path": "/home/trypton/.node-red/projects/raspipeso20_10_25/uibuilder",
"devDependencies": {},
"peerDependencies": {},
"_id": "uib_root@7.4.3",
"dependencies": {}
}
+19
View File
@@ -0,0 +1,19 @@
{
"version": "7.4.3",
"name": "uib_root",
"description": "Root configuration and data folder for uibuilder",
"scripts": {},
"homepage": "",
"bugs": "",
"author": "",
"license": "Apache-2.0",
"repository": "",
"uibuilder": {
"packages": {}
},
"path": "/home/trypton/.node-red/projects/raspipeso20_10_25/uibuilder",
"devDependencies": {},
"peerDependencies": {},
"_id": "uib_root@7.4.3",
"dependencies": {}
}
+178
View File
@@ -0,0 +1,178 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+77
View File
@@ -0,0 +1,77 @@
# uibuilder Template: Blank (Default)
> NOTE: You can replace the contents of this README with text that describes your UI.
This is about the simplest template you can get for uibuilder. Is is also (as of uibuilder v5+), the default template.
It does not use any frameworks and has no other dependencies. It demonstrates that you can use uibuilder purely with HTML/JavaScript or even just HTML and still easily build a simple, dynamic, data-driven user interface with the help of Node-RED.
All it does is load the uibuilder client library and connect to Node-RED.
## UI
Initially only shows an H1 heading with a sub-heading. However, it contains a `<div>` with the id "`more`" which is used by many of the examples in the Node-RED import library.
In addition, the `more` div uses uibuilder's `uib-topic` special attribute which allows it to be used as a target for messages sent from Node-RED. This is a useful feature that allows you to easily update the content of the page without having to write any JavaScript code. Send a message containing `{ topic: 'more', payload: 'Hello World' }` to the `uibuilder` node and the content of the `more` div will be updated with "Hello World". Note that the payload can contain HTML. As an example, use an inject node with `msg.payload` set to use a JSONata expression like `"<b style='background-color:var(--error)'>Hello!</b> This is a message from Node-RED at " & $moment()`. Don't forget to set `msg.topic` to `more` so that the uibuilder client library knows where to send the message.
> **WARNING**: Using the "more" topic completely overwrites the contents of the `more` div.
## Folders
* `/` - The root folder contains this file. It can be used for other things **but** it will not be served up in the Node-RED web server.
* `/src/` - the default folder that serves files as web resources. However, this can be changed to a different folder if desired.
* `/dist/` - the default folder for serving files as web resources where a build step is used. In that case, the `/src` folder is the source used by the build tool and `/dist` is the destination for the build (the "distribution" folder).
* `/routes/` - This folder can contain `.js` files defining routing middleware for uibuilder's ExpressJS web server.
* `/api/` - This folder can contain `.js` files defining REST API's specific to this uibuilder instance.
* `/types/` - Contains typescript definition files (`*.d.ts`) for the uibuilder client library. This is not used by uibuilder but can be used by your IDE to provide type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Remember to update these for new uibuilder versions.
The above folders will all pre-exist for the built-in uibuilder templates. The folders can safely be removed if not needed but one folder must exist to serve the web resources from (this cannot be the root folder).
The template only has files in the root and `src` folders. The `src` folder is the default used by uibuilder to serve up files to clients.
One reserved item in the root folder however will be a `package.json` file. This will be used in the future to help with build/compile steps. You can still use it yourself, just bear in mind that a future version of uibuilder will make use it as well. If you need to have any development packages installed to build your UI, don't forget to tell `npm` to save them as development dependencies not normal dependencies.
The `dist` folder should be used if you have a build step to convert your source code to something that browsers understand. So if you are using a build (compile) step to produce your production code, ensure that it is configured to use the `dist` folder as the output folder and that it creates at least an `index.html` file.
You can switch between the `src` and `dist` (or other) folders using the matching setting in the Editor. See uibuilder's advanced settings tab.
Also note that you can use **linked** folders and files in this folder structure. This can be handy if you want to maintain your code in a different folder somewhere or if your default build process needs to use sub-folders other than `src` and `dist`.(Though as of v6, you can specify any sub-folder to be served)
## Files in this template
* `package.json`: REQUIRED. Defines the basic structure, name, description of the project and defines any local development dependencies if any. Also works with `npm` allowing the installation of dev packages (such as build or linting tools).
* `README.md`: This file. Change this to describe your web app and provide documentation for it.
* `eslint.config.js`: A pre-configured configuration for the ESLINT tool. Helps when writing front-end code. Note that you need at least eslint v8+ installed for this to work.
* `LICENSE`: A copy of the Apache 2.0 license. Replace with a different license if needed. Always license your code. Apache 2.0 matches the licensing of uibuilder.
* `src/index.html`: REQUIRED. Contains your basic HTML and will be the file loaded and displayed in the browser when going to the uibuilder defined URL.
* `src/index.js`: Contains all of the logic for your UI. It must be linked to in the html file. Optional.
* `src/index.css`: Contains your custom CSS for styling. It must be linked to in the html file. Optional.
* `tsconfig.json`: A configuration file for TypeScript. This can be used by your IDE to provide descriptions, type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Uses the typescript definition files in the `/types` folder, remember to update these for new uibuilder versions.
Note that only the `package.json` and `index.html` files are actually _required_. uibuilder will not function as expected without them.
It is possible to use the index.html file simply as a link to other files but it must be present.
The other files are all optional. However, you will need to change the index.html file accordingly if you rename or remove them.
## Multiple HTML pages
uibuilder will happily serve up any number of web pages from a single instance. It will also make use of sub-folders. However, each folder should have an `index.html` file so that a URL that ends with the folder name will still work without error.
Note that each html file is a separate page and requires its own JavaScript and uibuilder library reference. When moving between pages, remember that every page is stand-alone, a new environment. You can share one `index.js` file between multiple pages if you prefer but each page will run a separate instance.
If multiple pages are connected to the same uibuilder instance, they will all get the same broadcast messages from Node-RED. So if you want to handle different messages on different pages, remember to filter them in your front-end JavaScript in `uibuilder.onChange('msg', ....)` function. Turn on the advanced flag for including a `msg._uib` property in output if you need to differentiate between pages and/or clients in Node-RED.
## URL endpoints
When specifying links in your HTML, CSS and JavaScript files, you should use relative URLs. e.g. `./index.mjs` will load that file from the `src` folder or wherever else you have told uibuilder to use.
When using uibuilder's server-side resources, you will generally use `../uibuilder/....`, for example `../uibuilder/uib-brand.min.css` as seen in the default `index.css` file. When accessing a front-end library being served by uibuilder, you can use the form `../uibuilder/vendor/....`. Use the "Full details" button in the uibuilder node to see all of the possible endpoints you may want to use.
## License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
This template may be used however you like. It is provided as a test template for uibuilder and is not intended to be a full template. You are free to use it as a starting point for your own template or to use it as-is if you find it useful.
View File
Binary file not shown.
View File
+121
View File
@@ -0,0 +1,121 @@
import { defineConfig } from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'
import jsdoc from 'eslint-plugin-jsdoc'
import stylistic from '@stylistic/eslint-plugin'
import html from 'eslint-plugin-html'
// Shared rules
const jsdocRules = {
'jsdoc/check-alignment': 'off',
// "jsdoc/check-indentation": ["warn", {"excludeTags":['example', 'description']}],
'jsdoc/check-indentation': 'off',
'jsdoc/check-param-names': 'warn',
'jsdoc/check-tag-names': ['warn', {
definedTags: ['typicalname', 'element', 'memberOf', 'slot', 'csspart'],
}],
'jsdoc/multiline-blocks': ['error', {
noZeroLineText: false,
}],
'jsdoc/no-multi-asterisk': 'off',
'jsdoc/no-undefined-types': ['error', {
definedTypes: ['JQuery', 'NodeListOf', 'ProxyHandler'],
}],
'jsdoc/tag-lines': 'off',
}
const stylisticRules = {
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true, }],
'@stylistic/comma-dangle': ['error', {
arrays: 'only-multiline',
objects: 'always',
imports: 'never',
exports: 'always-multiline',
functions: 'never',
importAttributes: 'never',
dynamicImports: 'never',
}],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/indent': ['error', 4, {
SwitchCase: 1,
}],
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/linebreak-style': ['error', 'unix'],
'@stylistic/lines-between-class-members': 'off',
'@stylistic/newline-per-chained-call': ['error', {
ignoreChainWithDepth: 2,
}],
'@stylistic/no-confusing-arrow': 'error',
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/semi': ['error', 'never'],
'@stylistic/space-before-function-paren': 'off',
'@stylistic/spaced-comment': ['error', 'always', {
line: {
exceptions: ['*', '#region', '#endregion'],
},
block: {
exceptions: ['*'],
},
}],
'@stylistic/space-in-parens': 'off',
'@stylistic/quotes': ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: 'always',
}],
}
const generalRules = {
'new-cap': 'error',
'no-else-return': 'error',
'no-empty': ['error', {
allowEmptyCatch: true,
}],
'no-unused-vars': 'off',
'no-useless-escape': 'off',
'no-var': 'warn',
'prefer-const': 'error',
}
export default defineConfig([
// Apply to all JavaScript files
{
files: ['**/*.js', '**/*.html'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'script', // Use script rather than ES modules
globals: {
...globals.browser,
// ...globals.node,
UibRouter: 'readonly',
uibuilder: 'readonly',
$: 'readonly',
$$: 'readonly',
},
},
plugins: {
'js': js,
'jsdoc': jsdoc,
'@stylistic': stylistic,
'html': html,
},
extends: [
js.configs.recommended,
jsdoc.configs['flat/recommended'],
stylistic.configs.recommended,
],
rules: {
...jsdocRules,
...stylisticRules,
...generalRules,
// 'no-empty': ['error', { 'allowEmptyCatch': true }],
},
},
// Specific rules for configuration files
{
files: ['eslint.config.mjs', '**/*.config.js', '**/*.config.mjs'],
languageOptions: {
sourceType: 'module', // Config files can use ES modules
},
},
])
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uib-blank",
"version": "2025-05-27",
"private": true,
"description": "This is about the simplest template you can get for uibuilder.",
"browser": "./src/index.js",
"scripts": {
"build": "echo \"No build process specified\""
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@stylistic/eslint-plugin": "^4.2.0",
"eslint": "^9.27.0",
"eslint-plugin-html": "^8.1.3",
"eslint-plugin-jsdoc": "^51.3.1",
"globals": "^16.1.0"
},
"keywords": ["uibuilder", "node-red", "node-red-contrib-uibuilder"],
"author": "Julian Knight (Totally Information)",
"license": "Apache-2.0",
"homepage": "https://github.com/TotallyInformation/node-red-contrib-uibuilder",
"bugs": "https://github.com/TotallyInformation/node-red-contrib-uibuilder/issues",
"repository": {
"type": "git",
"url": "https://github.com/TotallyInformation/node-red-contrib-uibuilder.git"
},
"browserslist": [
"> 0.5%",
"maintained versions",
"last 2 versions",
"not dead",
"not ie > 0"
]
}
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+540
View File
@@ -0,0 +1,540 @@
/* =============================== BASE GLOBAL =============================== */
body,
html {
margin: 0;
padding: 0;
font-family: "Segoe UI", sans-serif;
background-color: #fff;
color: #333;
}
.container {
max-width: 400px;
margin: auto;
padding: 20px;
}
#pantalla-tabla-general .container {
padding-left: 8px;
padding-right: 8px;
}
hr {
margin: 10px 0 20px;
border: 1px solid #ccc;
}
/* =============================== ENCABEZADO =============================== */
.header,
.encabezado {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.logo {
width: 40px;
height: 40px;
}
.titulo,
h3 {
text-align: center;
margin-top: 10px;
font-size: 26px;
font-weight: bold;
}
/* =============================== PANTALLAS SPA =============================== */
.pantalla {
display: none;
}
.pantalla.visible {
display: block;
}
/* =============================== BOTONES =============================== */
.btn {
padding: 16px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.btn.big {
width: 100%;
margin-top: 10px;
}
/* Colores */
.btn.yellow,
.btn.amarillo {
background: #f1c40f;
color: #444;
}
.btn.orange,
.btn.naranja {
background: #e67e22;
}
.btn.green,
.btn.verde {
background: #28a745;
}
.btn.teal,
.btn.verde-oscuro {
background: #049a81;
}
.btn.red,
.btn.rojo {
background: firebrick;
}
.btn.blue,
.btn.azul {
background: #3498db;
}
.btn.darkblue,
.btn.azuloscuro {
background: #0567a9;
}
.btn.aqua,
.btn.celeste {
background: #04fbc7;
color: #333;
}
.btn.purple,
.btn.morado {
background: #9b59b6;
}
.btn.gray,
.btn.gris {
background: #707b7c;
}
.btn.gold,
.btn.mostaza {
background: #DAB957;
color: #444;
}
/* =============================== BOTONERAS / GRID =============================== */
.grid-vertical {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 20px 0;
}
.botonera-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-width: 400px;
margin: 20px auto;
}
.botonera-vertical {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
margin: auto;
}
/* =============================== COMPONENTES VISUALES =============================== */
.circle {
width: 100px;
height: 100px;
margin: 20px auto;
border-radius: 50%;
background: #239b56;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.imagen {
display: flex;
justify-content: center;
margin: 12px 0;
}
.imagen img {
max-width: 85vw;
max-height: 43vh;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.matricula-nav {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 12px;
}
.placa {
background: white;
padding: 8px 16px;
border-radius: 10px;
font-size: 24px;
font-weight: bold;
color: #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 100px;
text-align: center;
}
.fecha {
text-align: center;
font-size: 14px;
font-weight: bold;
color: #555;
margin-top: 8px;
}
.camara-row {
display: flex;
justify-content: space-between;
font-weight: bold;
margin: 10px 0;
}
.desarrollo {
text-align: center;
margin: 20px 0;
}
/* =============================== TABLA EDITABLE =============================== */
table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 8px;
overflow: hidden;
table-layout: fixed;
word-wrap: break-word;
}
th,
td {
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
text-align: left;
}
th {
background-color: #007acc;
color: white;
font-weight: bold;
width: 40%;
}
td {
background-color: #fafafa;
}
td[contenteditable="true"] {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 25px;
}
.btn-actualizar,
.btn-cancelar {
padding: 12px 24px;
font-weight: bold;
font-size: 16px;
border-radius: 8px;
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn-actualizar {
background-color: #28a745;
color: white;
}
.btn-actualizar:hover {
background-color: #218838;
}
.btn-cancelar {
background-color: #dc3545;
color: white;
}
.btn-cancelar:hover {
background-color: #c82333;
}
/*----------------------------------------------- MODAL -------------------------------------*/
.modal {
display: none;
position: fixed;
z-index: 999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
background-color: #fff;
margin: 15% auto;
padding: 20px;
border-radius: 10px;
max-width: 300px;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.modal-buttons {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
/*----------------------------------------------- PANTALLA PRINCIPAL DEL TOTEM -------------------------------------*/
.peso-section {
display: flex;
justify-content: center;
align-items: baseline;
font-weight: bold;
margin: 5px 0 5px;
font-size: 18px;
}
.peso-display {
display: flex;
justify-content: center;
align-items: baseline;
gap: 8px;
font-size: 44px;
font-weight: bold;
margin: 20px 0;
text-align: center;
}
.peso-label {
margin-right: 10px;
font-size: 26px;
}
.peso-valor {
font-size: 90px;
color: #2c3e50;
font-weight: bold;
}
.peso-unidad {
margin-left: 8px;
font-size: 18px;
}
.status-line {
display: flex;
justify-content: space-between;
font-size: 16px;
margin: 8px 0;
}
.status-line .label {
font-weight: bold;
}
.status-line.small {
font-size: 14px;
color: #555;
}
.led-semaforo {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #ccc;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
margin-left: 10px;
display: inline-block;
}
.semaforo-display {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
.led-semaforo.led {
box-shadow: 0 0 12px currentColor, 0 0 3px currentColor inset;
}
.etiqueta {
min-width: 90px;
}
.labelMat{
font-size: 24px;
color: red;
font-weight: bold;
}
.labelPE {
font-size: 18px;
color: blue;
font-weight: bold;
}
.labelValue {
font-size: 18px;
color: blue;
}
#pantalla-barrera .container {
text-align: center;
}
/*-------------------------------------------------- PANTALLA DE LA BARRERA ---------------------------------------------------------------------*/
.barrera-icono {
margin: 20px auto;
height: 100px;
}
.barrera-icono img {
max-height: 100px;
}
/*-------------------------------------------------- TIPOS DE CONFIGURACION ---------------------------------------------------------------------*/
#tipo-configuracion .parametro-row {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
#tipo-configuracion input {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.parametro-row button {
font-size: 20px;
line-height: 20px;
padding: 0;
text-align: center;
}
.botonera-tipo {
margin-top: 15px;
display: flex;
justify-content: center;
gap: 10px;
}
/* Estilo base para la fila */
.parametro-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
width: 100%;
}
.parametro-row input.parametro {
flex: 1 1 50%;
min-width: 100px;
max-width: 300px;
padding: 4px;
font-size: 14px;
}
.parametro-row input.valor {
flex: 0 0 30%;
min-width: 60px;
max-width: 150px;
padding: 4px;
font-size: 14px;
text-align: center;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
transform: scale(1.8);
margin: 0;
}
.parametro-row button {
flex: 0 0 30px;
width: 30px;
height: 30px;
font-size: 14px;
padding: 0;
margin: 0;
}
@media (max-width: 600px) {
.parametro-row input.parametro {
flex: 1 1 40%;
font-size: 12px;
}
.parametro-row input.valor {
flex: 1 1 30%;
font-size: 12px;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
/*zoom: 1.5; */
transform: scale(1.5) ;
margin: 4px;
}
.parametro-row button {
flex: 0 0 25px;
width: 25px;
height: 25px;
font-size: 12px;
}
}
/*-------------------------------------------------- IMAGEN INICIAL DEL SNAPSHOT ---------------------------------------------------------------------*/
/*
#foto {
width: 65vw;
height: 30vh;
background-color: #eee;
border-radius: 10px;
object-fit: contain;
display: block;
margin: auto;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
*/
@import url("../uibuilder/uib-brand.min.css");
+317
View File
@@ -0,0 +1,317 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interfaz Totem</title>
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="../uibuilder/uibuilder.iife.min.js"></script>
<link rel="stylesheet" href="./index.css">
<script src="./index.js" defer></script>
</head>
<body>
<!-- ********************************************* PANTALLA INICIAL DEL TOTEM ******************************************************* -->
<div id="pantalla-principal" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h1 class="titulo">Sistema de Pesaje</h1>
</div>
<hr />
<div class="peso-display">
<span id="peso" class="peso-valor">0</span>
<span class="peso-unidad">Kg</span>
</div>
<div class="semaforo-display">
<span id="semaforo-color" class="led-semaforo led"></span>
</div>
<div class="status-line"><span class="label">Matrícula:</span> <span class="labelMat" id="matricula">----</span></div>
<div class="status-line"><span class="label">Peso Estable:</span> <span class="labelPE" id="peso-estable">----</span></div>
<div class="status-line"><span class="label">Vial:</span> <span class="labelValue" id="vial">--</span></div>
<div class="status-line"><span class="label">ID:</span> <span class="labelValue" id="totem-id">--</span></div>
<div class="status-line small"><span class="label">IP LAN:</span> <span class="labelValue" id="iplan">----</span></div>
<div class="status-line small"><span class="label">IP Supervisor:</span> <span class="labelValue" id="ipsupervisor">----</span></div>
<div class="botonera-grid">
<button class="btn verde big" data-action="menu-principal">CONFIG</button>
<button class="btn celeste big" data-action="menu-sistema">SISTEMA</button>
</div>
</div>
</div>
<!-- ************************************** MENU PRINCIPAL DE CONFIGURACION ******************************************* -->
<div id="pantalla-menu-principal" class="pantalla visible">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">CONFIGURACIÓN GESTOR SEMAFOROS</h3>
<hr>
<div class="grid-vertical">
<button class="btn blue" data-action="editar">EDITAR CONFIGURACION</button>
<button class="btn green" data-action="aplicar">APLICAR CONFIGURACION</button>
<!--
<button class="btn orange" data-action="pruebas">PRUEBAS DEL SISTEMA</button>
<button class="btn purple" data-action="otras">OTRAS ACCIONES</button>
-->
<button class="btn teal" data-action="salir">SALIR</button>
</div>
</div>
</div>
<!-- ******************************************* MENU EDITAR CONFIGURACION ****************************************** -->
<div id="pantalla-editar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">EDITAR CONFIGURACION</h3>
<hr />
<div class="botonera-grid">
<button data-action="general" class="btn amarillo">GENERAL</button>
<button data-action="conexion" class="btn celeste">CONEXIONES</button>
<!--
<button data-action="sensores" class="btn morado">SENSORES</button>
<button data-action="mensajes" class="btn gris">MENSAJES</button>
-->
<button data-action="tiempos" class="btn azul">TIEMPOS</button>
</div>
<div class="botonera-vertical">
<button data-action="salvar" class="btn verde">SALVAR MODIFICACIONES</button>
<button data-action="ayuda" class="btn mostaza">AYUDA</button>
<button data-action="cancelar" class="btn verde-oscuro">CANCELAR Y SALIR</button>
</div>
</div>
</div>
<!-- ****************************************MENU PRUEBAS DEL SISTEMA ****************************************** -->
<div id="pantalla-pruebas" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBAS DEL TOTEM</h3>
<hr>
<div class="grid-vertical">
<button class="btn yellow" data-action="semaforo">ENCENDER SEMAFORO</button>
<button class="btn aqua" data-action="barrera">ACTIVAR BARRERA</button>
<button class="btn orange" data-action="snapshot">PEDIR SNAPSHOT</button>
<button class="btn green" data-action="escuchar">ESCUCHAR CAMARA</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- MENU OTRAS ACCIONES --------------------------------------------------- -->
<div id="pantalla-otras" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">OTRAS ACCIONES</h3>
<hr>
<div class="desarrollo">
<h3 style="color:#2e86c1;">EN DESARROLLO</h3>
</div>
<div class="grid-vertical">
<button class="btn blue" data-action="enviar">ENVIAR SNAPSHOT</button>
<button class="btn aqua" data-action="reboot">REBOOT RPi</button>
<button class="btn red" data-action="apagar">APAGAR RPi</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PRUEBA DEL SEMAFORO --------------------------------------------------- -->
<div id="pantalla-semaforo" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBA DEL SEMAFORO</h3>
<hr>
<div class="circle" id="estado"></div>
<div class="grid-2col">
<button class="btn red" data-action="semaforo_on">ON</button>
<button class="btn green" data-action="semaforo_off">OFF</button>
</div>
<button class="btn teal big" data-action="salir-semaforo">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE SNAPSHOT DE LA CAMARA -------------------------------------------------- -->
<div id="pantalla-snapshot" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara">---</strong></span>
</div>
<div class="imagen"><img id="foto" src="" alt="Foto"></div>
<div class="matricula-nav">
<div class="placa" id="placa">----</div>
</div>
<div class="fecha" id="fecha">----</div>
<button class="btn teal big" data-action="salir-snapshot">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE ACTIVACION DE LA BARRERA -------------------------------------------------- -->
<div id="pantalla-barrera" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>ACTIVACION DE BARRERA</h2>
</div>
<!-- ICONO BARRERA -->
<div class="barrera-icono" id="barrera-icono">
<img id="icono-barrera" src="/barrera_cerrada.png" alt="Barrera" />
</div>
<!-- BOTONES -->
<div class="botonera-vertical">
<button class="btn verde big" data-action="activar-barrera">ACTIVAR BARRERA</button>
<button class="btn teal big" data-action="salir-barrera">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PANTALLA PARA ESCUCHAR LA CAMARA -------------------------------------------------- -->
<div id="pantalla-escuchar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara_escuchar">---</strong></span>
<span id="contador">0 / 0</span>
</div>
<div class="imagen"><img id="foto_escuchar" src="" alt="Foto"></div>
<div class="matricula-nav">
<button class="btn green" data-action="left">&lt;</button>
<div class="placa" id="placa_escuchar">----</div>
<button class="btn blue" data-action="right">&gt;</button>
</div>
<div class="fecha" id="fecha_escuchar">----</div>
<button class="btn teal big" data-action="salir-escuchar">SALIR</button>
</div>
</div>
<!-- ------------------------------- PANTALLA DE TABLA DINAMICA PARA MOSTRAR PARAMETROS DE CONFIGURACION------------------------------------------------->
<div id="pantalla-tabla-general" class="pantalla">
<div class="container">
<h2 style="text-align:center;">Parámetros del Sistema</h2>
<div class="button-container">
<table id="table2">
<thead>
<tr>
<th style="text-align:center;">Parámetro</th>
<th style="text-align:center;">Valor</th>
</tr>
</thead>
<tbody id="config-table-body">
<!-- Se rellena dinámicamente -->
</tbody>
</table>
</div>
<div class="button-container">
<button class="custom-btn btn-actualizar" onclick="sendRow()">ACTUALIZAR</button>
<button class="custom-btn btn-cancelar" onclick="sendCancel()">CANCELAR</button>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA TIPOS DE CONFIGURACION ---------------------------------------------------- -->
<div id="pantalla-tipos" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Tipos de Configuración</h2>
</div>
<hr>
<div class="botonera-grid">
<button class="btn blue" onclick="seleccionarTipo(1)">TIPO 1</button>
<button class="btn orange" onclick="seleccionarTipo(2)">TIPO 2</button>
<button class="btn yellow" onclick="seleccionarTipo(3)">TIPO 3</button>
<button class="btn celeste" onclick="seleccionarTipo(4)">TIPO 4</button>
<button class="btn purple" onclick="seleccionarTipo(5)">TIPO 5</button>
<button class="btn gris" onclick="seleccionarTipo(6)">TIPO 6</button>
</div>
<div class="botonera-vertical">
<button class="btn green" id="btn-volver-tipos">REGRESAR</button>
</div>
<div id="tipo-configuracion" style="margin-top:20px; display:none;">
<h3 id="titulo-tipo"></h3>
<div id="parametros"></div>
<div class="botonera-tipo">
<button id="btn-add-param" class="btn azul">+</button>
<button class="btn verde" id="btn-guardar-tipo">Guardar</button>
<button class="btn rojo" id="btn-cancelar-tipo">Cancelar</button>
</div>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA DE AYUDA ---------------------------------------------------- -->
<div id="pantalla-ayuda" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>AYUDA</h2>
</div>
<hr>
<iframe src="./ayuda.pdf" style="width:100%; height:600px; border:none;"></iframe>
<div style="margin-top: 20px; text-align: center;">
<button class="btn rojo" data-action="salir-ayuda">SALIR</button>
</div>
</div>
</div>
<!-- ------------------------------------- MODAL PARA POPUP ---------------------------------------------------- -->
<div id="confirm-modal" class="modal">
<div class="modal-content">
<p id="modal-text">¿Deseas guardar los cambios?</p>
<div class="modal-buttons">
<button id="btn-modal-si" class="btn verde" onclick="confirmGuardar()"></button>
<button id="btn-modal-no" class="btn rojo" onclick="cancelarGuardar()">No</button>
</div>
</div>
</div>
</body>
</html>
+494
View File
@@ -0,0 +1,494 @@
/* global uibuilder */
uibuilder.start();
// Navegación entre pantallas
function mostrarPantalla(id) {
document.querySelectorAll('.pantalla').forEach(div =>
div.classList.toggle('visible', div.id === id)
);
}
let currentTopic = "";
let result = [];
//****************************************************RECEPCION DE MENSAJES DESDE NODE_RED ***************************************************************** */
uibuilder.onChange('msg', msg => {
if (msg.payload && msg.payload.pantalla) {
mostrarPantalla('pantalla-' + msg.payload.pantalla);
}
if (msg.semaforo) {
document.getElementById('estado').style.backgroundColor = msg.semaforo;
}
if (msg.payload && msg.payload.pantalla === 'snapshot') {
// Limpiar datos visibles
document.getElementById('camara').textContent = '---';
document.getElementById('placa').textContent = '----';
document.getElementById('fecha').textContent = '----';
// Mostrar imagen vacía
document.getElementById('foto').src = "";
}
if (msg.payload && msg.payload.pantalla === 'escuchar' && msg.buttons != true) {
// Limpiar datos visibles
document.getElementById('camara_escuchar').textContent = '---';
document.getElementById('placa_escuchar').textContent = '----';
document.getElementById('fecha_escuchar').textContent = '----';
document.getElementById('contador').textContent = `0 / 0`;
// Mostrar imagen vacía
document.getElementById('foto_escuchar').src = "";
}
if (msg.hasOwnProperty("picture")) {
//uibuilder.send({ payload: msg.payload.Picture.Plate.PlateNumber, topic: msg.payload.Picture.SnapInfo.DeviceID });
if (msg.snapshot_cam) {
document.getElementById('foto').src = msg.picture;
document.getElementById('placa').textContent = msg.plate || '----';
document.getElementById('fecha').textContent = msg.plate_time || '';
document.getElementById('camara').textContent = msg.camera_id || '---';
}
else if (msg.escuchar_cam){
document.getElementById('foto_escuchar').src = msg.picture;
document.getElementById('placa_escuchar').textContent = msg.plate || '----';
document.getElementById('fecha_escuchar').textContent = msg.plate_time || '';
document.getElementById('camara_escuchar').textContent = msg.camera_id || '---';
const total = msg.snapshot_indexmax || 0;
const actual = (msg.snapshot_readindex || 0) + 1;
document.getElementById('contador').textContent = `${actual} / ${total}`;
}
}
else if (msg.payload?.pantalla === 'tipos') {
mostrarPantalla('pantalla-tipos');
if (msg.payload.tiposDatos) {
tiposDatos = msg.payload.tiposDatos;
}
}
if (msg.barrera) {
mostrarBarreraAbierta()
}
else{
mostrarBarreraCerrada()
}
// tabla dinamica
if (msg.topic == "general" || msg.topic == "conexion" || msg.topic == "camara" || msg.topic == "sensores" || msg.topic == "mensajes" || msg.topic == "tiempos"){
if (!msg.tabla || !Array.isArray(msg.tabla)) return;
const tbody = document.getElementById('config-table-body');
tbody.innerHTML = '';
currentTopic = msg.topic || 'general';
msg.tabla.forEach(item => { //generamos dinamixamente la tabla que vera el usuario en el dispositivo (PC o smartphone).
// notese que la celda 2 de cada fila es del tipo editable.
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = item.parametro;
const cell2 = document.createElement('td');
cell2.contentEditable = true;
//cell2.textContent = item.valor;
cell2.textContent = formatForCell(item.valor);
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row); // enviamos la tabla al elemento html (config-table-body)
});
}
// configuracion del pop up de notificaciones
if (msg.modalText) {
document.getElementById('modal-text').textContent = msg.modalText;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
if (msg.modalType === "notificacion") {
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
} else {
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = msg.modalCallback || null;
}
document.getElementById('confirm-modal').style.display = 'block';
}
// ======================================== Datos para la Pantalla Principal ========================================================================
if (msg.hasOwnProperty("peso")) document.getElementById('peso').textContent = msg.peso || 0;
//if (msg.semaforo_color) document.getElementById('semaforo-color').style.backgroundColor = msg.semaforo_color;
const led = document.getElementById('semaforo-color');
if (msg.semaforo_color) {
led.style.backgroundColor = msg.semaforo_color;
led.classList.add('led');
} else {
led.style.backgroundColor = '#ccc';
led.classList.remove('led');
}
if (msg.hasOwnProperty("matricula")) document.getElementById('matricula').textContent = msg.matricula;
if (msg.hasOwnProperty("peso_estable_flag")) document.getElementById('peso-estable').textContent = msg.peso_estable_flag ? 'SI' : 'NO';
if (msg.hasOwnProperty("vial")) document.getElementById('vial').textContent = msg.vial;
if (msg.hasOwnProperty("totem_id")) document.getElementById('totem-id').textContent = msg.totem_id;
if (msg.hasOwnProperty("iplan")) document.getElementById('iplan').textContent = msg.iplan;
if (msg.hasOwnProperty("ipsupervisor")) document.getElementById('ipsupervisor').textContent = msg.ipsupervisor;
});
//****************************************************************************************************************************************************** */
// Detectar clics en botones
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action]')) {
const action = ev.target.getAttribute('data-action');
console.log('⏺ Acción botón:', action);
uibuilder.send({ payload: { seccion: action } });
}
});
// Mostrar pantalla tipos
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action="tipos"]')) {
mostrarPantalla('pantalla-tipos');
}
});
document.getElementById('btn-add-param').addEventListener('click', agregarParametro);
document.getElementById('btn-guardar-tipo').addEventListener('click', () => {
window.modalCallback = guardarTipo;
document.getElementById('modal-text').textContent = `¿Deseas guardar los cambios de Tipo ${tipoActual}?`;
document.getElementById('confirm-modal').style.display = 'block';
});
document.getElementById('btn-cancelar-tipo').addEventListener('click', () => {
uibuilder.send({ payload: { accion: "cancelar_tipo", tipo: tipoActual } });
volverATipos();
});
document.getElementById('btn-volver-tipos').addEventListener('click', () => {
volverATipos(); // esto limpia la tabla con los parametros del Tipo en el que estemos
uibuilder.send({ payload: { seccion: "volver_tipos" } });
});
let tipoActual = null;
let tiposDatos = {}; // aquí se guarda la configuración temporal
//******************************************* FUNCIONES ******************************************************** */
// Funcion para asegurar que se vea el array en la pantalla de configuracion. Si no se hace esto se ve los numeros sin los []
// y luego al pasarlo al archivo no se puede identificar si es un array
function formatForCell(v) {
if (v === null || v === undefined) return '';
if (typeof v === 'object') return JSON.stringify(v); // arrays y objetos
return String(v);
}
// Acciones a partir de botones en html
function sendRow() {// Aqui llega cuando se hace click en el boton Actualizar de las tablas dinamicas
// recoge el contenido de la tabla en ese instante y saca Notificacion de guardar con opcion Si o No
const rows = document.querySelectorAll('#config-table-body tr');
result = []; // inicializamos result para poner exactamente la tabla actual
/*
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// normaliza a booleano si corresponde
if (valor === "true") valor = true;
if (valor === "false") valor = false;
result.push({ parametro, valor });
});
*/
//Modificacion de la lectura de la tabla para asegurar que los numeros son mantenidos como numeros y no como texto
// al igual que los valores booleanos.
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// 1) Boolean
if (/^(true|false)$/i.test(valor)) {
valor = valor.toLowerCase() === "true";
// 2) JSON Array u Object: "[3,1,2]" o {"a":1}
} else if (
(valor.startsWith("[") && valor.endsWith("]")) ||
(valor.startsWith("{") && valor.endsWith("}"))
) {
try {
valor = JSON.parse(valor);
// Opcional: si es array, intenta convertir strings numéricas a números
if (Array.isArray(valor)) {
valor = valor.map(v => {
if (typeof v === "string" && /^-?\d+$/.test(v)) return parseInt(v, 10);
if (typeof v === "string" && /^-?\d+\.\d+$/.test(v)) return parseFloat(v);
return v;
});
}
} catch (e) {
// Si no es JSON válido, se queda como string
}
// 3) Int
} else if (/^-?\d+$/.test(valor)) {
valor = parseInt(valor, 10);
// // 4) Float
} else if (/^-?\d+\.\d+$/.test(valor)) {
valor = parseFloat(valor);
}
result.push({ parametro, valor });
});
// Mostramos directamente la confirmación y definimos la callback. "topic" contiene el nombre de la tabla donde se guardan los valores que vienen en "result"
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({
payload: { table: result, seccion: "respuesta" },
topic: currentTopic,
origen: "actualizar"
});
});
}
function sendCancel() { // boton Cancelar de las tablas dinamicas
uibuilder.send({ payload: {seccion:'cancel-table'}, topic: currentTopic });
}
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
document.getElementById('confirm-modal').style.display = 'none';
if (typeof window.modalCallback === 'function') {
const cb = window.modalCallback;
window.modalCallback = null; // limpia para la siguiente vez
cb(); // ejecuta
} else if (typeof window.modalCallback === 'string') {
uibuilder.send({ payload: { table: result }, modalCallback: window.modalCallback, origen: "modal" });
}
}
/*
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
uibuilder.send({ payload: { table: result }, topic: currentTopic, origen: "modal-tabla" });
document.getElementById('confirm-modal').style.display = 'none';
}
*/
function cancelarGuardar() { // Respuesta Negativa del Modal de tabla dinamica
result = [];
document.getElementById('confirm-modal').style.display = 'none';
}
// Funciones para icono barrera
function mostrarBarreraAbierta() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_abierta.png";
/*
setTimeout(() => {
mostrarBarreraCerrada();
}, 1000); // 1 segundo
*/
}
function mostrarBarreraCerrada() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_cerrada.png";
}
//************************* FUNCIONES DE TIPOS DE CONFIGURACION *****************************/
function seleccionarTipo(n) {
tipoActual = n;
document.getElementById('titulo-tipo').textContent = `TIPO ${n}`;
document.getElementById('tipo-configuracion').style.display = 'block';
const contenedor = document.getElementById('parametros');
contenedor.innerHTML = '';
const datos = tiposDatos[n] || []; // ya es un array
datos.forEach(d => {
const row = crearFilaParametro(d.parametro, d.valor);
contenedor.appendChild(row);
});
}
function agregarParametro() {
const contenedor = document.getElementById('parametros');
if (contenedor.children.length >= 8) return;
const row = crearFilaParametro('', '');
contenedor.appendChild(row);
}
function crearFilaParametro(parametro, valor) {
const div = document.createElement('div');
div.className = 'parametro-row';
// Input para el nombre del parámetro
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
// Input para el valor: checkbox o texto
let inputValor;
if (valor === true || valor === false || valor === "true" || valor === "false") {
inputValor = document.createElement('input');
inputValor.type = 'checkbox';
inputValor.checked = (valor === true || valor === "true");
} else {
inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
}
inputParam.classList.add('parametro');
inputValor.classList.add('valor');
// Botón para eliminar la fila
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.flex = '0 0 40px';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.style.marginLeft = '4px';
btnDelete.style.zIndex = '1';
btnDelete.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
return false;
});
// Añadir los elementos a la fila
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function crearFilaParametro_viejo(parametro, valor) { // Crea un nuevo elemento en la tabla del Tipo que estemos trabajando con dos campos y un boton de borrado
const div = document.createElement('div');
div.className = 'parametro-row';
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
const inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.onclick = () => { // gestion de la alerta para el borrado, haciendo uso del modal que tenemos en html
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
};
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function guardarTipo() {
const contenedor = document.getElementById('parametros');
const filas = contenedor.querySelectorAll('.parametro-row');
const datos = [];
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
let valor;
if (val.type === 'checkbox') {
valor = val.checked;
} else {
valor = val.value.trim();
}
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor });
}
});
/*
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor: val.value.trim() });
}
});
*/
tiposDatos[tipoActual] = datos;
uibuilder.send({ payload: { tipo: tipoActual, datos }, topic: "tipoconfig" }); // el topic nos sirve para seleccionar en el proximo nodo la funcion de guardar los datos
volverATipos(); // 🔷 salimos de la pantalla del tipo actual
}
function volverATipos() {
document.getElementById('tipo-configuracion').style.display = 'none';
tipoActual = null;
}
//==========================================FUNCIONES PARA MANEJAR LAS NOTIFICACIONES ======================================================
function mostrarConfirmacion(texto, callback) {
document.getElementById('modal-text').textContent = texto;
// Restablece estado normal del modal
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = callback;
document.getElementById('confirm-modal').style.display = 'block';
}
function mostrarNotificacion(texto) {
document.getElementById('modal-text').textContent = texto;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
document.getElementById('confirm-modal').style.display = 'block';
}
/*
Cuando necesitemos un modal clásico (Sí/No), por ejemplo para confirmar actualización:
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({ payload: { table: result, seccion:"respuesta" }, topic: currentTopic, origen: "actualizar" });
});
Cuando necesitemos simplemente notificar un error:
mostrarNotificacion("La IP introducida es inválida, por favor corrígela.");
*/
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"baseUrl": "./src",
"typeRoots": ["./types"]
},
"include": ["types", "src/**/*.js"]
}
+12
View File
@@ -0,0 +1,12 @@
/// <reference path="./uibuilder.module.d.ts" />
/**
* Make the uibuilder instance globally available (from uibuilder.module.d.ts)
* @version 7.3.0
*/
declare global {
// Use typeof import to reference the Uib class from the module
const uibuilder: import("./uibuilder.module").Uib;
}
export {};
+647
View File
@@ -0,0 +1,647 @@
/**
* Type definitions for uibuilder.module.js
* WCAG 2.2 AA, ESLint v9, Shift-Left security, and project conventions applied.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export type HtmlString = string
/** Column metadata for tables */
export interface ColumnDefinition {
index: number,
hasName: boolean,
title: string,
name?: string,
key?: string | number,
dataType?: 'string' | 'date' | 'number' | 'html',
editable?: boolean,
}
/** Options for building HTML tables */
export interface TableOptions {
cols?: ColumnDefinition[],
parent?: HTMLElement | string,
allowHTML?: boolean,
}
/** Options for tblAddListener */
export interface TableListenerOptions {
eventScope?: 'row' | 'cell',
returnType?: 'text' | 'html',
pad?: number,
send?: boolean,
logLevel?: string | number,
eventType?: string,
}
/** Options for notification */
export interface NotificationConfig {
title?: string,
body?: string,
return?: boolean,
[key: string]: any,
}
/**
* Uibuilder main class
* @typicalname uibuilder
* @description The client-side Front-End JavaScript for uibuilder in HTML Module form.
* Provides a number of global objects that can be used in your own JavaScript.
* See the docs folder `./docs/uibuilder.module.md` for details of how to use this fully.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export class Uib {
/**
* Static metadata for the Uibuilder client
*/
static _meta: {
version: string,
type: string,
displayName: string,
}
/** Client ID set by uibuilder on connect */
clientId: string
/** The collection of cookies provided by uibuilder */
cookies: Record<string, string>
/** Copy of last control msg object received from server */
ctrlMsg: object
/** Is Socket.IO client connected to the server? */
ioConnected: boolean
/** Is the library running from a minified version? */
isMinified: boolean
/** Is the browser tab containing this page visible or not? */
isVisible: boolean
/** Remember the last page (re)load/navigation type: navigate, reload, back_forward, prerender */
lastNavType: string
/** Max msg size that can be sent over Socket.IO - updated by "client connect" msg receipt */
maxHttpBufferSize: number
/** Last std msg received from Node-RED */
msg: object
/** Number of messages sent to server since page load */
msgsSent: number
/** Number of messages received from server since page load */
msgsReceived: number
/** Number of control messages sent to server since page load */
msgsSentCtrl: number
/** Number of control messages received from server since page load */
msgsCtrlReceived: number
/** Is the client online or offline? */
online: boolean
/** Last control msg object sent via uibuilder.send() */
sentCtrlMsg: object
/** Last std msg object sent via uibuilder.send() */
sentMsg: object
/** Placeholder to track time offset from server, see fn socket.on(ioChannels.server ...) */
serverTimeOffset: number | null
/** Placeholder for a socket error message */
socketError: string | null
/** Tab identifier from session storage */
tabId: string
/** Actual name of current page (set in constructor) */
pageName: string | null
/** Is the DOMPurify library loaded? Updated in start() */
purify: boolean
/** Is the Markdown-IT library loaded? Updated in start() */
markdown: boolean
/** Current URL hash. Initial set is done from start->watchHashChanges via a set to make it watched */
urlHash: string
/** Default originator node id - empty string by default */
originator: string
/** Optional default topic to be included in outgoing standard messages */
topic?: string
/** Either undefined or a reference to a uib router instance. Set by uibrouter, do not set manually. */
uibrouterinstance?: any
/** Set by uibrouter, do not set manually */
uibrouter_CurrentRoute?: any
/** Internal: auto-send ready flag */
autoSendReady: boolean
/** Node-RED setting (via cookie) */
httpNodeRoot: string
/** Socket.IO namespace - unique to each uibuilder node instance */
ioNamespace: string
/** Socket.IO path */
ioPath: string
/** Starting delay factor for subsequent reconnect attempts */
retryFactor: number
/** Starting retry ms period for manual socket reconnections workaround */
retryMs: number
/** Prefix for all uib-related localStorage */
storePrefix: string
/** Whether uibuilder client has started */
started: boolean
/** Socket.IO connection options */
socketOptions: object
// --- Getters/Setters ---
logLevel: number
meta: typeof Uib._meta
/**
* Set uibuilder properties to a new value - works on any property except _* or #*
* Also triggers any event listeners.
* @param prop Any uibuilder property who's name does not start with a _ or #
* @param val The set value of the property or a string declaring that a protected property cannot be changed
* @param store If true, the variable is also saved to the browser localStorage if possible
* @param autoload If true & store is true, on load, uib will try to restore the value from the store automatically
* @returns Input value
*/
set(prop: string, val: any, store?: boolean, autoload?: boolean): any
/**
* Get the value of a uibuilder property
* @param prop The name of the property to get as long as it does not start with a _ or #
* @returns The current value of the property
*/
get(prop: string): any
/**
* Write to localStorage if possible. Console error output if can't write
* Also uses this.storePrefix
* @param id localStorage var name to be used (prefixed with 'uib_')
* @param value value to write to localstore
* @param autoload If true, on load, uib will try to restore the value from the store
* @returns True if succeeded else false
*/
setStore(id: string, value: any, autoload?: boolean): boolean
/**
* Attempt to get and re-hydrate a key value from localStorage
* @param id The key of the value to attempt to retrieve
* @returns The re-hydrated value of the key or null if key not found, undefined on error
*/
getStore(id: string): any
/**
* Remove a given id from the uib keys in localStorage
* @param id The key to remove
*/
removeStore(id: string): void
/**
* Returns a list of uibuilder properties (variables) that can be watched with onChange
* @returns List of uibuilder managed variables
*/
getManagedVarList(): Record<string, string>
/**
* Returns a list of currently watched variables
* @returns List of watched variable names
*/
getWatchedVars(): string[]
/**
* Register on-change event listeners for uibuilder tracked properties
* @param prop The property of uibuilder that we want to monitor
* @param callback The function that will run when the property changes, parameter is the new value of the property after change
* @returns A reference to the callback to cancel
*/
onChange(prop: string, callback: (val: any) => void): number
/**
* Cancel a previously registered onChange event listener
* @param prop The property name
* @param cbRef The callback reference number
*/
cancelChange(prop: string, cbRef: number): void
/**
* Register a change callback for a specific msg.topic
* @param topic The msg.topic we want to listen for
* @param callback The function that will run when an appropriate msg is received
* @returns A reference to the callback to cancel
*/
onTopic(topic: string, callback: (msg: any) => void): number
/**
* Cancel a previously registered onTopic event listener
* @param topic The topic name
* @param cbRef The callback reference number
*/
cancelTopic(topic: string, cbRef: number): void
/**
* Returns a new array containing the intersection of the 2 input arrays
* @param a1 Array to check
* @param a2 Array to intersect
* @returns The intersection of the 2 arrays (may be an empty array)
*/
arrayIntersect<T>(a1: T[], a2: T[]): T[]
/**
* Copies a uibuilder variable to the browser clipboard
* @param varToCopy The name of the uibuilder variable to copy to the clipboard
*/
copyToClipboard(varToCopy: string): void
/**
* Does the chosen CSS Selector currently exist?
* @param cssSelector Required. CSS Selector to examine for visibility
* @param msg Optional, default=true. If true also sends a message back to Node-RED
* @returns True if the element exists
*/
elementExists(cssSelector: string, msg?: boolean): boolean
/**
* Format a number using the INTL standard library
* @param value Number to format
* @param decimalPlaces Number of decimal places to include
* @param intl standard locale spec, e.g. "ja-JP" or "en-GB"
* @param opts INTL library options object
* @returns formatted number
*/
formatNumber(value: number, decimalPlaces?: number, intl?: string, opts?: object): string
/**
* Attempt to get rough size of an object
* @param obj Any serialisable object
* @returns Rough size of object in bytes or undefined
*/
getObjectSize(obj: any): number | undefined
/**
* Returns true if a uibrouter instance is loaded, otherwise returns false
* @returns true if uibrouter instance loaded else false
*/
hasUibRouter(): boolean
/**
* Only keep the URL Hash & ignoring query params
* @param url URL to extract the hash from
* @returns Just the route id
*/
keepHashFromUrl(url: string): string
/**
* Custom logging function
* @param args Arguments to log
*/
log(...args: any[]): void
/**
* Makes a null or non-object into an object. If thing is already an object.
* If not null, moves "thing" to {payload:thing}
* @param thing Thing to check
* @param property property that "thing" is moved to if not null and not an object. Default='payload'
* @returns Object
*/
makeMeAnObject(thing: any, property?: string): object
/**
* Navigate to a new page or a new route (hash)
* @param url URL to navigate to. Can be absolute or relative (to current page) or just a hash for a route change
* @returns The new window.location string
*/
navigate(url: string): Location
/**
* Convert a string attribute into a variable/constant reference
* Used to resolve data sources in attributes
* @param path The string path to resolve, must be relative to the `window` global scope
* @returns The resolved data source or null
*/
resolveDataSource(path: string): any
/**
* Fast but accurate number rounding
* @param num The number to be rounded
* @param decimalPlaces Number of DP's to round to
* @returns Rounded number
*/
round(num: number, decimalPlaces: number): number
/**
* Set the default originator. Set to '' to ignore. Used with uib-sender.
* @param originator A Node-RED node ID to return the message to
*/
setOriginator(originator?: string): void
/**
* HTTP Ping/Keep-alive - makes a call back to uibuilder's ExpressJS server and receives a 204 response
* Can be used to keep sessions alive.
* @param ms Repeat interval in ms
*/
setPing(ms?: number): void
/**
* Convert JSON to Syntax Highlighted HTML
* @param json A JSON/JavaScript Object
* @returns Object reformatted as highlighted HTML
*/
syntaxHighlight(json: object): HtmlString
/**
* Returns true/false or a default value for truthy/falsy and other values
* @param val The value to test
* @param deflt Default value to use if the value is not truthy/falsy
* @returns The truth! Or the default
*/
truthy(val: any, deflt: any): boolean | any
/**
* Joins all arguments as a URL string
* @param paths URL fragments
* @returns Joined URL string
*/
urlJoin(...paths: string[]): string
/**
* Turn on/off/toggle sending URL hash changes back to Node-RED
* @param toggle Optional on/off/etc
* @returns True if we will send a msg to Node-RED on a hash change
*/
watchUrlHash(toggle?: any): boolean
/**
* DEPRECATED FOR NOW - wasn't working properly.
* Is the chosen CSS Selector currently visible to the user? NB: Only finds the FIRST element of the selection.
* @returns False
*/
elementIsVisible(): false
// --- UI handlers ---
/**
* Simplistic jQuery-like document CSS query selector, returns an HTML Element.
* If the selected element is a <template>, returns the first child element.
* @param cssSelector A CSS Selector that identifies the element to return
* @returns Selected HTML element or null
*/
$: (cssSelector: string) => HTMLElement | null
/**
* CSS query selector that returns ALL found selections as an array of elements.
* @param cssSelector A CSS Selector that identifies the elements to return
* @returns Array of DOM elements/nodes. Array is empty if selector is not found.
*/
$$: (cssSelector: string) => HTMLElement[]
/**
* Reference to the full ui library
*/
$ui: any
/**
* Add one or several class names to an element
* @param classNames Single or array of classnames
* @param el HTML Element to add class(es) to
*/
addClass(classNames: string | string[], el: HTMLElement): void
/**
* Apply a source template tag to a target html element
* @param source The source element
* @param target The target element
* @param onceOnly If true, the source will be adopted (the source is moved)
*/
applyTemplate(source: HTMLElement, target: HTMLElement, onceOnly: boolean): void
/**
* Builds an HTML table from an array (or object) of objects
* @param data Input data array or object
* @param opts Table options
* @returns Output HTML Element
*/
buildHtmlTable(data: object[] | object, opts?: TableOptions): HTMLTableElement | HTMLParagraphElement
/**
* Directly add a table to a parent element.
* @param data Input data array or object
* @param opts Build options
*/
createTable(data?: object[] | any[], opts?: TableOptions): void
/**
* Converts markdown text input to HTML if the Markdown-IT library is loaded
* Otherwise simply returns the text
* @param mdText The input markdown string
* @returns HTML (if Markdown-IT library loaded and parse successful) or original text
*/
convertMarkdown(mdText: string): string
/**
* ASYNC: Include HTML fragment, img, video, text, json, form data, pdf or anything else from an external file or API
* @param url The URL of the source file to include
* @param uiOptions Object containing properties recognised by the _uiReplace function. Must at least contain an id
*/
include(url: string, uiOptions: object): Promise<void>
/**
* Attach a new remote script to the end of HEAD synchronously
* @param url The url to be used in the script src attribute
*/
loadScriptSrc(url: string): void
/**
* Attach a new remote stylesheet link to the end of HEAD synchronously
* @param url The url to be used in the style link href attribute
*/
loadStyleSrc(url: string): void
/**
* Attach a new text script to the end of HEAD synchronously
* @param textFn The text to be loaded as a script
*/
loadScriptTxt(textFn: string): void
/**
* Attach a new text stylesheet to the end of HEAD synchronously
* @param textFn The text to be loaded as a stylesheet
*/
loadStyleTxt(textFn: string): void
/**
* Load a dynamic UI from a JSON web response
* @param url URL that will return the ui JSON
*/
loadui(url: string): void
/**
* Remove All, 1 or more class names from an element
* @param classNames Single or array of classnames. If undefined, "" or null, remove all classes
* @param el HTML Element to remove class(es) from
*/
removeClass(classNames: string | string[] | undefined | null, el: HTMLElement): void
/**
* Replace or add an HTML element's slot from text or an HTML string
* WARNING: Executes <script> tags! And will process <style> tags.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param slot The slot content we are trying to add/replace (defaults to empty string)
*/
replaceSlot(el: Element, slot: any): void
/**
* Replace or add an HTML element's slot from a Markdown string
* Only does something if the markdownit library has been loaded to window.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param component The component we are trying to add/replace
*/
replaceSlotMarkdown(el: Element, component: any): void
/**
* Sanitise HTML to make it safe - if the DOMPurify library is loaded
* Otherwise just returns that HTML as-is.
* @param html The input HTML string
* @returns The sanitised HTML or the original if DOMPurify not loaded
*/
sanitiseHTML(html: string): string
/**
* Add table event listener that returns the text or html content of either the full row or a single cell
* @param tblSelector The table CSS Selector
* @param options Additional options
* @param out A variable reference that will be updated with the output data upon a click event
*/
tblAddListener(tblSelector: string, options?: TableListenerOptions, out?: object): void
/**
* Add a row to a table element in the DOM.
* @param tbl The table element or selector to add the row to
* @param rowData The data for the new row (object or array)
* @param options Optional configuration for row creation
* @returns The created HTMLTableRowElement
*/
tblAddRow(tbl: string | HTMLTableElement, rowData: object | any[], options?: object): HTMLTableRowElement
/**
* Remove a row from a table element in the DOM.
* @param tbl The table element or selector to remove the row from
* @param rowIndex The index of the row to remove
* @param options Optional configuration for row removal
*/
tblRemoveRow(tbl: string | HTMLTableElement, rowIndex: number, options?: object): void
/**
* Show a dialog (notification or alert) in the UI.
* @param type The dialog type: 'notify' or 'alert'
* @param ui The UI configuration object for the dialog
* @param msg Optional message object to include
*/
showDialog(type: 'notify' | 'alert', ui: object, msg?: object): void
/**
* Apply a UI definition (JSON) to the current page.
* @param json The UI definition object
*/
ui(json: object): void
/**
* Get properties or values from UI elements matching a selector.
* @param cssSelector The CSS selector for the elements
* @param propName Optional property name to retrieve
* @returns Array of property values or elements
*/
uiGet(cssSelector: string, propName?: string): any[]
/**
* Enhance a DOM element with a UI component definition.
* @param el The element to enhance
* @param component The component definition or configuration
*/
uiEnhanceElement(el: any, component: any): void
// --- DOM/HTML cache ---
/**
* Clear the cached HTML content from memory or storage.
*/
clearHtmlCache(): void
/**
* Restore HTML content from the cache into the DOM.
*/
restoreHtmlFromCache(): void
/**
* Save the current HTML content to the cache for later restoration.
*/
saveHtmlCache(): void
// --- Message Handling ---
/**
* Send a standard message to Node-RED via Socket.IO.
* @param msg The message object to send
* @param originator Optional Node-RED node ID to return the message to
*/
send(msg: object, originator?: string): void
/**
* Send a message to a specific room via Socket.IO.
* @param room The room name
* @param msg The message to send
*/
sendRoom(room: string, msg: any): void
/**
* Join a Socket.IO room.
* @param room The room name to join
*/
joinRoom(room: string): void
/**
* Leave a Socket.IO room.
* @param room The room name to leave
*/
leaveRoom(room: string): void
/**
* Send a control message to Node-RED via Socket.IO.
* @param msg The control message object to send
*/
sendCtrl(msg: object): void
/**
* Send a custom message on a specific channel via Socket.IO.
* @param channel The custom channel name
* @param msg The message object to send
*/
sendCustom(channel: string, msg: object): void
/**
* Upload a file to the server via Socket.IO.
* @param file The file to upload
* @param meta Optional metadata to send with the file
*/
uploadFile(file: File, meta?: object): void
// --- Socket.IO ---
/**
* Connect the Socket.IO client to the server.
*/
connect(): void
/**
* Disconnect the Socket.IO client from the server.
*/
disconnect(): void
// --- Startup ---
/**
* Start the uibuilder client, initializing all features and connections.
* @param options Optional startup options
*/
start(options?: object): void
// --- Show/hide ---
/**
* Show or hide the message area in the UI.
* @param showHide If true, show the message area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the message area is shown, false if hidden
*/
showMsg(showHide?: boolean, parent?: string): boolean
/**
* Show or hide the status area in the UI.
* @param showHide If true, show the status area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the status area is shown, false if hidden
*/
showStatus(showHide?: boolean, parent?: string): boolean
// --- Watchers ---
/**
* Watch a DOM element for changes and optionally send updates to Node-RED.
* @param cssSelector The CSS selector to watch
* @param startStop Start, stop, or toggle the watcher
* @param send If true, send updates to Node-RED
* @param showLog If true, log watcher activity
* @returns True if watching, false otherwise
*/
uiWatch(cssSelector: string, startStop?: boolean | 'toggle', send?: boolean, showLog?: boolean): boolean
/**
* Watch the DOM for changes (e.g., for dynamic UI updates).
* @param startStop Start or stop watching
*/
watchDom(startStop: boolean): void
// --- Notifications ---
/**
* Show a notification or alert in the UI.
* @param config Notification configuration or string message
* @returns A promise resolving to the notification event, or null
*/
notify(config: NotificationConfig | string): Promise<Event> | null
// --- Clipboard ---
/**
* Copy a uibuilder variable's value to the clipboard.
* @param varToCopy The name of the uibuilder variable to copy
*/
copyToClipboard(varToCopy: string): void
}
/** The default uibuilder instance */
declare const uibuilder: Uib
export { uibuilder }
export default uibuilder
+178
View File
@@ -0,0 +1,178 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+77
View File
@@ -0,0 +1,77 @@
# uibuilder Template: Blank (Default)
> NOTE: You can replace the contents of this README with text that describes your UI.
This is about the simplest template you can get for uibuilder. Is is also (as of uibuilder v5+), the default template.
It does not use any frameworks and has no other dependencies. It demonstrates that you can use uibuilder purely with HTML/JavaScript or even just HTML and still easily build a simple, dynamic, data-driven user interface with the help of Node-RED.
All it does is load the uibuilder client library and connect to Node-RED.
## UI
Initially only shows an H1 heading with a sub-heading. However, it contains a `<div>` with the id "`more`" which is used by many of the examples in the Node-RED import library.
In addition, the `more` div uses uibuilder's `uib-topic` special attribute which allows it to be used as a target for messages sent from Node-RED. This is a useful feature that allows you to easily update the content of the page without having to write any JavaScript code. Send a message containing `{ topic: 'more', payload: 'Hello World' }` to the `uibuilder` node and the content of the `more` div will be updated with "Hello World". Note that the payload can contain HTML. As an example, use an inject node with `msg.payload` set to use a JSONata expression like `"<b style='background-color:var(--error)'>Hello!</b> This is a message from Node-RED at " & $moment()`. Don't forget to set `msg.topic` to `more` so that the uibuilder client library knows where to send the message.
> **WARNING**: Using the "more" topic completely overwrites the contents of the `more` div.
## Folders
* `/` - The root folder contains this file. It can be used for other things **but** it will not be served up in the Node-RED web server.
* `/src/` - the default folder that serves files as web resources. However, this can be changed to a different folder if desired.
* `/dist/` - the default folder for serving files as web resources where a build step is used. In that case, the `/src` folder is the source used by the build tool and `/dist` is the destination for the build (the "distribution" folder).
* `/routes/` - This folder can contain `.js` files defining routing middleware for uibuilder's ExpressJS web server.
* `/api/` - This folder can contain `.js` files defining REST API's specific to this uibuilder instance.
* `/types/` - Contains typescript definition files (`*.d.ts`) for the uibuilder client library. This is not used by uibuilder but can be used by your IDE to provide type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Remember to update these for new uibuilder versions.
The above folders will all pre-exist for the built-in uibuilder templates. The folders can safely be removed if not needed but one folder must exist to serve the web resources from (this cannot be the root folder).
The template only has files in the root and `src` folders. The `src` folder is the default used by uibuilder to serve up files to clients.
One reserved item in the root folder however will be a `package.json` file. This will be used in the future to help with build/compile steps. You can still use it yourself, just bear in mind that a future version of uibuilder will make use it as well. If you need to have any development packages installed to build your UI, don't forget to tell `npm` to save them as development dependencies not normal dependencies.
The `dist` folder should be used if you have a build step to convert your source code to something that browsers understand. So if you are using a build (compile) step to produce your production code, ensure that it is configured to use the `dist` folder as the output folder and that it creates at least an `index.html` file.
You can switch between the `src` and `dist` (or other) folders using the matching setting in the Editor. See uibuilder's advanced settings tab.
Also note that you can use **linked** folders and files in this folder structure. This can be handy if you want to maintain your code in a different folder somewhere or if your default build process needs to use sub-folders other than `src` and `dist`.(Though as of v6, you can specify any sub-folder to be served)
## Files in this template
* `package.json`: REQUIRED. Defines the basic structure, name, description of the project and defines any local development dependencies if any. Also works with `npm` allowing the installation of dev packages (such as build or linting tools).
* `README.md`: This file. Change this to describe your web app and provide documentation for it.
* `eslint.config.js`: A pre-configured configuration for the ESLINT tool. Helps when writing front-end code. Note that you need at least eslint v8+ installed for this to work.
* `LICENSE`: A copy of the Apache 2.0 license. Replace with a different license if needed. Always license your code. Apache 2.0 matches the licensing of uibuilder.
* `src/index.html`: REQUIRED. Contains your basic HTML and will be the file loaded and displayed in the browser when going to the uibuilder defined URL.
* `src/index.js`: Contains all of the logic for your UI. It must be linked to in the html file. Optional.
* `src/index.css`: Contains your custom CSS for styling. It must be linked to in the html file. Optional.
* `tsconfig.json`: A configuration file for TypeScript. This can be used by your IDE to provide descriptions, type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Uses the typescript definition files in the `/types` folder, remember to update these for new uibuilder versions.
Note that only the `package.json` and `index.html` files are actually _required_. uibuilder will not function as expected without them.
It is possible to use the index.html file simply as a link to other files but it must be present.
The other files are all optional. However, you will need to change the index.html file accordingly if you rename or remove them.
## Multiple HTML pages
uibuilder will happily serve up any number of web pages from a single instance. It will also make use of sub-folders. However, each folder should have an `index.html` file so that a URL that ends with the folder name will still work without error.
Note that each html file is a separate page and requires its own JavaScript and uibuilder library reference. When moving between pages, remember that every page is stand-alone, a new environment. You can share one `index.js` file between multiple pages if you prefer but each page will run a separate instance.
If multiple pages are connected to the same uibuilder instance, they will all get the same broadcast messages from Node-RED. So if you want to handle different messages on different pages, remember to filter them in your front-end JavaScript in `uibuilder.onChange('msg', ....)` function. Turn on the advanced flag for including a `msg._uib` property in output if you need to differentiate between pages and/or clients in Node-RED.
## URL endpoints
When specifying links in your HTML, CSS and JavaScript files, you should use relative URLs. e.g. `./index.mjs` will load that file from the `src` folder or wherever else you have told uibuilder to use.
When using uibuilder's server-side resources, you will generally use `../uibuilder/....`, for example `../uibuilder/uib-brand.min.css` as seen in the default `index.css` file. When accessing a front-end library being served by uibuilder, you can use the form `../uibuilder/vendor/....`. Use the "Full details" button in the uibuilder node to see all of the possible endpoints you may want to use.
## License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
This template may be used however you like. It is provided as a test template for uibuilder and is not intended to be a full template. You are free to use it as a starting point for your own template or to use it as-is if you find it useful.
View File
View File
+121
View File
@@ -0,0 +1,121 @@
import { defineConfig } from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'
import jsdoc from 'eslint-plugin-jsdoc'
import stylistic from '@stylistic/eslint-plugin'
import html from 'eslint-plugin-html'
// Shared rules
const jsdocRules = {
'jsdoc/check-alignment': 'off',
// "jsdoc/check-indentation": ["warn", {"excludeTags":['example', 'description']}],
'jsdoc/check-indentation': 'off',
'jsdoc/check-param-names': 'warn',
'jsdoc/check-tag-names': ['warn', {
definedTags: ['typicalname', 'element', 'memberOf', 'slot', 'csspart'],
}],
'jsdoc/multiline-blocks': ['error', {
noZeroLineText: false,
}],
'jsdoc/no-multi-asterisk': 'off',
'jsdoc/no-undefined-types': ['error', {
definedTypes: ['JQuery', 'NodeListOf', 'ProxyHandler'],
}],
'jsdoc/tag-lines': 'off',
}
const stylisticRules = {
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true, }],
'@stylistic/comma-dangle': ['error', {
arrays: 'only-multiline',
objects: 'always',
imports: 'never',
exports: 'always-multiline',
functions: 'never',
importAttributes: 'never',
dynamicImports: 'never',
}],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/indent': ['error', 4, {
SwitchCase: 1,
}],
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/linebreak-style': ['error', 'unix'],
'@stylistic/lines-between-class-members': 'off',
'@stylistic/newline-per-chained-call': ['error', {
ignoreChainWithDepth: 2,
}],
'@stylistic/no-confusing-arrow': 'error',
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/semi': ['error', 'never'],
'@stylistic/space-before-function-paren': 'off',
'@stylistic/spaced-comment': ['error', 'always', {
line: {
exceptions: ['*', '#region', '#endregion'],
},
block: {
exceptions: ['*'],
},
}],
'@stylistic/space-in-parens': 'off',
'@stylistic/quotes': ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: 'always',
}],
}
const generalRules = {
'new-cap': 'error',
'no-else-return': 'error',
'no-empty': ['error', {
allowEmptyCatch: true,
}],
'no-unused-vars': 'off',
'no-useless-escape': 'off',
'no-var': 'warn',
'prefer-const': 'error',
}
export default defineConfig([
// Apply to all JavaScript files
{
files: ['**/*.js', '**/*.html'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'script', // Use script rather than ES modules
globals: {
...globals.browser,
// ...globals.node,
UibRouter: 'readonly',
uibuilder: 'readonly',
$: 'readonly',
$$: 'readonly',
},
},
plugins: {
'js': js,
'jsdoc': jsdoc,
'@stylistic': stylistic,
'html': html,
},
extends: [
js.configs.recommended,
jsdoc.configs['flat/recommended'],
stylistic.configs.recommended,
],
rules: {
...jsdocRules,
...stylisticRules,
...generalRules,
// 'no-empty': ['error', { 'allowEmptyCatch': true }],
},
},
// Specific rules for configuration files
{
files: ['eslint.config.mjs', '**/*.config.js', '**/*.config.mjs'],
languageOptions: {
sourceType: 'module', // Config files can use ES modules
},
},
])
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uib-blank",
"version": "2025-05-27",
"private": true,
"description": "This is about the simplest template you can get for uibuilder.",
"browser": "./src/index.js",
"scripts": {
"build": "echo \"No build process specified\""
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@stylistic/eslint-plugin": "^5.2.2",
"eslint": "^9.27.0",
"eslint-plugin-html": "^8.1.3",
"eslint-plugin-jsdoc": "^54.1.1",
"globals": "^16.1.0"
},
"keywords": ["uibuilder", "node-red", "node-red-contrib-uibuilder"],
"author": "Julian Knight (Totally Information)",
"license": "Apache-2.0",
"homepage": "https://github.com/TotallyInformation/node-red-contrib-uibuilder",
"bugs": "https://github.com/TotallyInformation/node-red-contrib-uibuilder/issues",
"repository": {
"type": "git",
"url": "https://github.com/TotallyInformation/node-red-contrib-uibuilder.git"
},
"browserslist": [
"> 0.5%",
"maintained versions",
"last 2 versions",
"not dead",
"not ie > 0"
]
}
View File
+110
View File
@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pantalla de Ayuda</title>
<style>
body { font-family: Arial, sans-serif; }
.container { max-width: 800px; margin: auto; padding: 20px; }
</style>
</head>
<body>
<!-- -------------------------------------- PANTALLA DE AYUDA --------------------------------------------------- -->
<div id="pantalla-ayuda" class="pantalla">
<div class="container">
<div class="header">
<h2>Trypton Software</h2>
</div>
<h3>AYUDA - PARÁMETROS CONFIGURABLES</h3>
<hr>
<div style="max-height: 70vh; overflow-y: auto; text-align: justify; font-size: 14px; padding: 0 5px;">
<p>Totem</p>
<p>AYUDA EN LA DEFINICIÓN DE PARÁMETROS CONFIGURABLES</p>
<p>Menu Configuración GENERAL</p>
<p><strong>Tipo_Configuracion</strong> = 4. Indica el TIPO de configuración que debemos aplicar durante la configuración. Los TIPOS se pueden definir en el menu de configuración(Config/Editar Configuracion/Tipos de Configuración), el usuario puede definir hasta ocho parámetros en cada TIPO. El usuario debe definir los parámetros y sus valores los cuales durante la carga del archivo de configuración se tomaran en cuenta por encima de lo que digan los parámetros de configuracion que hayamos definido en los botones GENERAL, CAMARA, SENSORES… Si no queremos aplicar ningún TIPO entonces colocamos Tipo_Configuracion = 0.</p>
<p><strong>ID_obligatorio</strong>: Si true, indica que es obligatorio mostrar una identificación autorizada (RFID / QR) para poder continuar con el proceso de pesaje.</p>
<p><strong>detector_obligatorio</strong>: Si true, indica que aunque el vehículo haya sido detectado frente al totem, por otro medio (QR, Botón,...) es obligatorio que el vehículo sea visto por el detector, de otra manera el proceso queda detenido.</p>
<p><strong>boton_obligatorio</strong>: Si true, indica que el conductor debe hacer click en el botón de salida que se presenta en la pantalla para que el proceso de pesada pueda finalizar, de otra manera el proceso queda detenido.</p>
<p><strong>firma_obligatoria</strong>: Si true, el Totem solicitara por pantalla la firma del conductor una vez que se haya detectado el peso estable en la bascula.</p>
<p><strong>identificacion_esp</strong>: Si true, indica que en caso que la matrícula no haya podido se leida, el vehículo puede continuar hasta el totem pero debe presentar un QR o una tarjeta RFID autorizados.</p>
<p><strong>edicion_matricula</strong>: Si true, indica que en caso de lectura erronea de la matrícula el Totem presentara un editor de matrícula para quie el conductor pueda editar la matrícula.</p>
<p><strong>botonsalida_activo</strong>: Si true, indica que el Totem presentara el botón de validacion o botón de salida en pantalla.</p>
<p><strong>menu_activo</strong>: Si true, indica que el usuario recibira un menu en pantalla para seleccionar el sitio de donde viene y el contenido del material que transporta.</p>
<p><strong>barrera_habilitada</strong>: Si true indica que cuándo la pesada haya finalizado la barrera será activada con un pulso de 1 segundo (tiempo = relay2_pulse)</p>
<p><strong>switch_2_Totem_page</strong>: Si true, indica que en caso de quedar la pantalla de configuración en la pantalla el sistema cambiara a la pantalla del Totem en cuanto detecte un nuevo valor de peso. Si esta en false, la pantalla de configuración debe quitarse manualmente desde la pantalla, desde el móvil o desde un PC.</p>
<p><strong>dias_en_BD</strong> = 2. El número indica los dias que pueden mantenerse los archivos relativos a los movimientos en BD una vez que estos hayan sido enviados al servidor.</p>
<p><strong>peso_minimo</strong> = 1000. Valor en Kg leido de la bascula para decir que hay un vehículo entrando.</p>
<p><strong>audio_level</strong> = 20. Nivel de audio inicial de los mensajes.</p>
<p><strong>mante_code_RFID</strong> = 3284727558. Codigo para abrir ventana de mantenimiento.</p>
<p><strong>mante_code_QR</strong> = "this is the key to open the maintenance screen".</p>
<p><strong>init_screen</strong> = true. Habilita inicialización de la pantalladel Nodered un tiempo despues del arranque.</p>
<p><strong>totem_serial</strong> = 20. Serial asignado a este dispositivo.</p>
<p><strong>numero_clicks</strong> = 3. Número de clicks para entrar en ventana de mantenimiento. (viejo)</p>
<p><strong>botonsalida_color_def</strong> = "#026d73". Color del fondo del botón de validación.</p>
<p><strong>botonsalida_txt_color_def</strong> = "orange". Color del texto del botón de validación.</p>
<p>Menu Configuración COMUNICACIONES</p>
<p><strong>modo_totem</strong> = A, B, C, o D. Modo de operación del Totem</p>
<p><strong>A</strong>: QR/RFID; Camara ANPR; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>B</strong>: Camara ANPR; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>C</strong>: QR/RFID; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>D</strong>: Botón Validación.</p>
<p><strong>ipdevice</strong> = "10.148.171.100".</p>
<p><strong>iprouter</strong> = "10.148.171.1".</p>
<p><strong>dhcp_flag</strong> = true. Modo de trabajo de asignacion de IP. Si = false -> IP Fija</p>
<p><strong>websocket_puerto</strong> = 7000.No utilizado ya que no se puede asignar mediante variable al nodo WS.</p>
<p><strong>websocket_url</strong> = "10.148.171.13". IP del supervisor</p>
<p><strong>movimientosxmqtt</strong> = Si true, indica que el envío de los datos de los movimientos se enviaran vía mqtt. La definición del broker se hace dentro del nodo de mqtt en el editor de nodered.</p>
<p><strong>movimientosxapi</strong> = Si true, indica que el envío de los datos de los movimientos se enviaran vía API. En este caso la url de la api se define en el siguiente parámetro.</p>
<p><strong>url_API=https</strong>: //urlapi.servidor-urbaser.com/api. URL del servidor receptor de datos de movimiento.</p>
<p><strong>path_images</strong> = /home/trypton/node-red/dahua_images/. Carpeta para guardar imagenes matrícula.</p>
<p><strong>path_firmas</strong> = /home/trypton/node-red/firmas/. Carpeta para guardar imagenes de las firmas.</p>
<p><strong>totem_qty</strong> = 2. Nº de Totems en la bascula.</p>
<p><strong>totem_id_remoto</strong> = "totem_4".</p>
<p><strong>totem_id</strong> = "totem_3".  ID asignado a este totem.</p>
<p><strong>numero_vial</strong> = 3. Número del vial asignado al Totem.</p>
<p><strong>ip_totem_pareja</strong> = "10.148.171.126".</p>
<p><strong>supervisor</strong> = false. Indica si el Totem trabaja con un Supervisor o en autonomo.</p>
<p>Menu Configuración MENSAJES</p>
<p><strong>mensaje2</strong> = "Pesada terminada".</p>
<p><strong>mensaje1</strong> = "Peso Estable".</p>
<p><strong>botonsalida_txt_def</strong> = "OK". Texto del botón de Validación</p>
<p><strong>botonsalida_mensaje1_def</strong> = "PESADA FINALIZADA". Mensaje que aparece encima del botón de validación cuándo la pesada ha finalizado..</p>
<p><strong>men_pesaje_listo</strong> = "PESAJE LISTO".</p>
<p><strong>men_peso_estable</strong> = "PESO ESTABLE". Aparece debajo del peso</p>
<p><strong>men_identificacion</strong> = "Acerque QR / tarjeta de ID". Aparece parte inferior de la pantalla</p>
<p><strong>men_salida</strong> = "PUEDE MOVER EL VEHICULO".</p>
<p><strong>id_titulo_matricula</strong> = "MATRICULA".</p>
<p><strong>id_titulo_id</strong> = "TARJETA ID".</p>
<p><strong>id_leido</strong> = "ID leido y enviado". Aparece una vez leido QR / RFID.</p>
<p><strong>id_no_leido</strong> = "ID NO leido". Indica que no se ha recibido QR / RFID en esta pesada.</p>
<p>Menu Configuración CAMARA</p>
<p><strong>camara_matricula</strong> = true. Indica que usamos camara ANPR.</p>
<p><strong>camaraID</strong> = "CAMARA_ENTRADA". Nombre de la camara asociada a este Totem</p>
<p><strong>camara_IP</strong> = "10.148.171.33". IP de la camara asociada a este Totem.</p>
<p>Menu Configuración SENSORES</p>
<p><strong>tipo_de_lector</strong> = "ER-80". Puede ser Kimaldi u otro.</p>
<p><strong>rfid_invertido</strong> = false. Indica si es necesario darle la vuelta al código hexadecimal leido del RFID.</p>
<p><strong>sensor_distancia_local</strong> = true. Indica que disponemos de sensor de distancia local.</p>
<p><strong>sensor_distancia_remoto</strong> = true. Indica que disponemos de sensor de distancia remoto, es decir que el Totem pareja (en la misma bascula) envía valor del sensor de distancia.</p>
<p><strong>distance_min</strong> = 2000. Distancia a partir de la cual indica que no hay ningún objeto</p>
<p><strong>min_cnt_in_ok</strong> = 1. Nº de veces seguidas que el sensor encuentra distancia < distance_minpara indicar que hay presencia de un objeto.</p>
<p><strong>min_cnt_out_ok</strong> = 4. Nº de veces seguidas que el sensor encuentra distancia > distance_minpara indicar que hay ausencia de objeto.</p>
<p>Menu Configuración TIEMPOS</p>
<p><strong>timer1</strong> = 1000.Este parametro actualmente no se utiliza.</p>
<p><strong>tiempo_mensaje</strong> = 10000.Este parametro actualmente no se utiliza.</p>
<p><strong>relay2_pulse</strong> = 1000. Nº milisegundos del pulso de apertura de la barrera.</p>
<p><strong>config_window_time</strong> = 500. Nº de milisegundos para leer los clicks.</p>
<p><strong>time_max_2_bascula</strong> = 10000. Tiempo desde recepcion del ANPR0 (antes que el vehículo entre en bascula) hasta la activacion de la bascula.</p>
<p><strong>wait_time_to_send_plate</strong> = 3000. Tiempo de espera para enviar la matricula, después de haber recibido ANPR0valido, por si llega un nuevo snapshot antes de 3s</p>
<p><strong>max_wait_time_to_send_plate</strong> = 8000. Tiempo de espera para enviar la matricula, en caso deNOtener ANPR0validopor si llega un nuevo ANPR.</p>
<p><strong>max_time_bascula_sensor</strong> = 4000. Tiempo maximo permitido entre activacion de bascula y activacion del detector de vehículos.Actualmente no se utiliza.</p>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+540
View File
@@ -0,0 +1,540 @@
/* =============================== BASE GLOBAL =============================== */
body,
html {
margin: 0;
padding: 0;
font-family: "Segoe UI", sans-serif;
background-color: #fff;
color: #333;
}
.container {
max-width: 400px;
margin: auto;
padding: 20px;
}
#pantalla-tabla-general .container {
padding-left: 8px;
padding-right: 8px;
}
hr {
margin: 10px 0 20px;
border: 1px solid #ccc;
}
/* =============================== ENCABEZADO =============================== */
.header,
.encabezado {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.logo {
width: 40px;
height: 40px;
}
.titulo,
h3 {
text-align: center;
margin-top: 10px;
font-size: 26px;
font-weight: bold;
}
/* =============================== PANTALLAS SPA =============================== */
.pantalla {
display: none;
}
.pantalla.visible {
display: block;
}
/* =============================== BOTONES =============================== */
.btn {
padding: 16px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.btn.big {
width: 100%;
margin-top: 10px;
}
/* Colores */
.btn.yellow,
.btn.amarillo {
background: #f1c40f;
color: #444;
}
.btn.orange,
.btn.naranja {
background: #e67e22;
}
.btn.green,
.btn.verde {
background: #28a745;
}
.btn.teal,
.btn.verde-oscuro {
background: #049a81;
}
.btn.red,
.btn.rojo {
background: firebrick;
}
.btn.blue,
.btn.azul {
background: #3498db;
}
.btn.darkblue,
.btn.azuloscuro {
background: #0567a9;
}
.btn.aqua,
.btn.celeste {
background: #04fbc7;
color: #333;
}
.btn.purple,
.btn.morado {
background: #9b59b6;
}
.btn.gray,
.btn.gris {
background: #707b7c;
}
.btn.gold,
.btn.mostaza {
background: #DAB957;
color: #444;
}
/* =============================== BOTONERAS / GRID =============================== */
.grid-vertical {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 20px 0;
}
.botonera-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-width: 400px;
margin: 20px auto;
}
.botonera-vertical {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
margin: auto;
}
/* =============================== COMPONENTES VISUALES =============================== */
.circle {
width: 100px;
height: 100px;
margin: 20px auto;
border-radius: 50%;
background: #239b56;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.imagen {
display: flex;
justify-content: center;
margin: 12px 0;
}
.imagen img {
max-width: 85vw;
max-height: 43vh;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.matricula-nav {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 12px;
}
.placa {
background: white;
padding: 8px 16px;
border-radius: 10px;
font-size: 24px;
font-weight: bold;
color: #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 100px;
text-align: center;
}
.fecha {
text-align: center;
font-size: 14px;
font-weight: bold;
color: #555;
margin-top: 8px;
}
.camara-row {
display: flex;
justify-content: space-between;
font-weight: bold;
margin: 10px 0;
}
.desarrollo {
text-align: center;
margin: 20px 0;
}
/* =============================== TABLA EDITABLE =============================== */
table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 8px;
overflow: hidden;
table-layout: fixed;
word-wrap: break-word;
}
th,
td {
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
text-align: left;
}
th {
background-color: #007acc;
color: white;
font-weight: bold;
width: 40%;
}
td {
background-color: #fafafa;
}
td[contenteditable="true"] {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 25px;
}
.btn-actualizar,
.btn-cancelar {
padding: 12px 24px;
font-weight: bold;
font-size: 16px;
border-radius: 8px;
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn-actualizar {
background-color: #28a745;
color: white;
}
.btn-actualizar:hover {
background-color: #218838;
}
.btn-cancelar {
background-color: #dc3545;
color: white;
}
.btn-cancelar:hover {
background-color: #c82333;
}
/*----------------------------------------------- MODAL -------------------------------------*/
.modal {
display: none;
position: fixed;
z-index: 999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
background-color: #fff;
margin: 15% auto;
padding: 20px;
border-radius: 10px;
max-width: 300px;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.modal-buttons {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
/*----------------------------------------------- PANTALLA PRINCIPAL DEL TOTEM -------------------------------------*/
.peso-section {
display: flex;
justify-content: center;
align-items: baseline;
font-weight: bold;
margin: 5px 0 5px;
font-size: 18px;
}
.peso-display {
display: flex;
justify-content: center;
align-items: baseline;
gap: 8px;
font-size: 44px;
font-weight: bold;
margin: 20px 0;
text-align: center;
}
.peso-label {
margin-right: 10px;
font-size: 26px;
}
.peso-valor {
font-size: 90px;
color: #2c3e50;
font-weight: bold;
}
.peso-unidad {
margin-left: 8px;
font-size: 18px;
}
.status-line {
display: flex;
justify-content: space-between;
font-size: 16px;
margin: 8px 0;
}
.status-line .label {
font-weight: bold;
}
.status-line.small {
font-size: 14px;
color: #555;
}
.led-semaforo {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #ccc;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
margin-left: 10px;
display: inline-block;
}
.semaforo-display {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
.led-semaforo.led {
box-shadow: 0 0 12px currentColor, 0 0 3px currentColor inset;
}
.etiqueta {
min-width: 90px;
}
.labelMat{
font-size: 24px;
color: red;
font-weight: bold;
}
.labelPE {
font-size: 18px;
color: blue;
font-weight: bold;
}
.labelValue {
font-size: 18px;
color: blue;
}
#pantalla-barrera .container {
text-align: center;
}
/*-------------------------------------------------- PANTALLA DE LA BARRERA ---------------------------------------------------------------------*/
.barrera-icono {
margin: 20px auto;
height: 100px;
}
.barrera-icono img {
max-height: 100px;
}
/*-------------------------------------------------- TIPOS DE CONFIGURACION ---------------------------------------------------------------------*/
#tipo-configuracion .parametro-row {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
#tipo-configuracion input {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.parametro-row button {
font-size: 20px;
line-height: 20px;
padding: 0;
text-align: center;
}
.botonera-tipo {
margin-top: 15px;
display: flex;
justify-content: center;
gap: 10px;
}
/* Estilo base para la fila */
.parametro-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
width: 100%;
}
.parametro-row input.parametro {
flex: 1 1 50%;
min-width: 100px;
max-width: 300px;
padding: 4px;
font-size: 14px;
}
.parametro-row input.valor {
flex: 0 0 30%;
min-width: 60px;
max-width: 150px;
padding: 4px;
font-size: 14px;
text-align: center;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
transform: scale(1.8);
margin: 0;
}
.parametro-row button {
flex: 0 0 30px;
width: 30px;
height: 30px;
font-size: 14px;
padding: 0;
margin: 0;
}
@media (max-width: 600px) {
.parametro-row input.parametro {
flex: 1 1 40%;
font-size: 12px;
}
.parametro-row input.valor {
flex: 1 1 30%;
font-size: 12px;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
/*zoom: 1.5; */
transform: scale(1.5) ;
margin: 4px;
}
.parametro-row button {
flex: 0 0 25px;
width: 25px;
height: 25px;
font-size: 12px;
}
}
/*-------------------------------------------------- IMAGEN INICIAL DEL SNAPSHOT ---------------------------------------------------------------------*/
/*
#foto {
width: 65vw;
height: 30vh;
background-color: #eee;
border-radius: 10px;
object-fit: contain;
display: block;
margin: auto;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
*/
@import url("../uibuilder/uib-brand.min.css");
+312
View File
@@ -0,0 +1,312 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interfaz Totem</title>
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="../uibuilder/uibuilder.iife.min.js"></script>
<link rel="stylesheet" href="./index.css">
<script src="./index.js" defer></script>
</head>
<body>
<!-- -------------------------------------- PANTALLA INICIAL DEL TOTEM --------------------------------------------------- -->
<div id="pantalla-principal" class="pantalla visible">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h1 class="titulo">Sistema de Pesaje</h1>
</div>
<hr />
<div class="peso-display">
<span id="peso" class="peso-valor">0</span>
<span class="peso-unidad">Kg</span>
</div>
<div class="semaforo-display">
<span id="semaforo-color" class="led-semaforo led"></span>
</div>
<div class="status-line"><span class="label">Matrícula:</span> <span class="labelMat" id="matricula">----</span></div>
<div class="status-line"><span class="label">Peso Estable:</span> <span class="labelPE" id="peso-estable">----</span></div>
<div class="status-line"><span class="label">Vial:</span> <span class="labelValue" id="vial">--</span></div>
<div class="status-line"><span class="label">ID:</span> <span class="labelValue" id="totem-id">--</span></div>
<div class="status-line small"><span class="label">IP LAN:</span> <span class="labelValue" id="iplan">----</span></div>
<div class="status-line small"><span class="label">IP Supervisor:</span> <span class="labelValue" id="ipsupervisor">----</span></div>
<div class="botonera-grid">
<button class="btn verde big" data-action="menu-principal">CONFIG</button>
<button class="btn celeste big" data-action="menu-sistema">SISTEMA</button>
</div>
</div>
</div>
<!-- -------------------------------------- MENU PRINCIPAL DE CONFIGURACION --------------------------------------------------- -->
<div id="pantalla-menu-principal" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">CONFIGURACIÓN DEL TOTEM</h3>
<hr>
<div class="grid-vertical">
<button class="btn blue" data-action="editar">EDITAR CONFIGURACION</button>
<button class="btn green" data-action="aplicar">APLICAR CONFIGURACION</button>
<button class="btn orange" data-action="pruebas">PRUEBAS DEL SISTEMA</button>
<button class="btn purple" data-action="otras">OTRAS ACCIONES</button>
<button class="btn teal" data-action="salir">SALIR</button>
</div>
</div>
</div>
<!-- --------------------------------------MENU EDITAR CONFIGURACION--------------------------------------------------- -->
<div id="pantalla-editar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">EDITAR CONFIGURACION</h3>
<hr />
<div class="botonera-grid">
<button data-action="general" class="btn amarillo">GENERAL</button>
<button data-action="conexion" class="btn celeste">COMUNICACIONES</button>
<button data-action="sensores" class="btn morado">SENSORES</button>
<button data-action="mensajes" class="btn gris">MENSAJES</button>
<button data-action="tiempos" class="btn azul">TIEMPOS</button>
</div>
<div class="botonera-vertical">
<button data-action="salvar" class="btn verde">SALVAR MODIFICACIONES</button>
<button data-action="ayuda" class="btn mostaza">AYUDA</button>
<button data-action="cancelar" class="btn verde-oscuro">CANCELAR Y SALIR</button>
</div>
</div>
</div>
<!-- --------------------------------------MENU PRUEBAS DEL SISTEMA --------------------------------------------------- -->
<div id="pantalla-pruebas" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBAS DEL TOTEM</h3>
<hr>
<div class="grid-vertical">
<button class="btn yellow" data-action="semaforo">ENCENDER SEMAFORO</button>
<button class="btn aqua" data-action="barrera">ACTIVAR BARRERA</button>
<button class="btn orange" data-action="snapshot">PEDIR SNAPSHOT</button>
<button class="btn green" data-action="escuchar">ESCUCHAR CAMARA</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- MENU OTRAS ACCIONES --------------------------------------------------- -->
<div id="pantalla-otras" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">OTRAS ACCIONES</h3>
<hr>
<div class="desarrollo">
<h3 style="color:#2e86c1;">EN DESARROLLO</h3>
</div>
<div class="grid-vertical">
<button class="btn blue" data-action="enviar">ENVIAR SNAPSHOT</button>
<button class="btn aqua" data-action="reboot">REBOOT RPi</button>
<button class="btn red" data-action="apagar">APAGAR RPi</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PRUEBA DEL SEMAFORO --------------------------------------------------- -->
<div id="pantalla-semaforo" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBA DEL SEMAFORO</h3>
<hr>
<div class="circle" id="estado"></div>
<div class="grid-2col">
<button class="btn red" data-action="semaforo_on">ON</button>
<button class="btn green" data-action="semaforo_off">OFF</button>
</div>
<button class="btn teal big" data-action="salir-semaforo">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE SNAPSHOT DE LA CAMARA -------------------------------------------------- -->
<div id="pantalla-snapshot" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara">---</strong></span>
</div>
<div class="imagen"><img id="foto" src="" alt="Foto"></div>
<div class="matricula-nav">
<div class="placa" id="placa">----</div>
</div>
<div class="fecha" id="fecha">----</div>
<button class="btn teal big" data-action="salir-snapshot">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE ACTIVACION DE LA BARRERA -------------------------------------------------- -->
<div id="pantalla-barrera" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>ACTIVACION DE BARRERA</h2>
</div>
<!-- ICONO BARRERA -->
<div class="barrera-icono" id="barrera-icono">
<img id="icono-barrera" src="/barrera_cerrada.png" alt="Barrera" />
</div>
<!-- BOTONES -->
<div class="botonera-vertical">
<button class="btn verde big" data-action="activar-barrera">ACTIVAR BARRERA</button>
<button class="btn teal big" data-action="salir-barrera">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PANTALLA PARA ESCUCHAR LA CAMARA -------------------------------------------------- -->
<div id="pantalla-escuchar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara_escuchar">---</strong></span>
<span id="contador">0 / 0</span>
</div>
<div class="imagen"><img id="foto_escuchar" src="" alt="Foto"></div>
<div class="matricula-nav">
<button class="btn green" data-action="left">&lt;</button>
<div class="placa" id="placa_escuchar">----</div>
<button class="btn blue" data-action="right">&gt;</button>
</div>
<div class="fecha" id="fecha_escuchar">----</div>
<button class="btn teal big" data-action="salir-escuchar">SALIR</button>
</div>
</div>
<!-- ------------------------------- PANTALLA DE TABLA DINAMICA PARA MOSTRAR PARAMETROS DE CONFIGURACION------------------------------------------------->
<div id="pantalla-tabla-general" class="pantalla">
<div class="container">
<h2 style="text-align:center;">Parámetros del Sistema</h2>
<div class="button-container">
<table id="table2">
<thead>
<tr>
<th style="text-align:center;">Parámetro</th>
<th style="text-align:center;">Valor</th>
</tr>
</thead>
<tbody id="config-table-body">
<!-- Se rellena dinámicamente -->
</tbody>
</table>
</div>
<div class="button-container">
<button class="custom-btn btn-actualizar" onclick="sendRow()">ACTUALIZAR</button>
<button class="custom-btn btn-cancelar" onclick="sendCancel()">CANCELAR</button>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA TIPOS DE CONFIGURACION ---------------------------------------------------- -->
<div id="pantalla-tipos" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Tipos de Configuración</h2>
</div>
<hr>
<div class="botonera-grid">
<button class="btn blue" onclick="seleccionarTipo(1)">TIPO 1</button>
<button class="btn orange" onclick="seleccionarTipo(2)">TIPO 2</button>
<button class="btn yellow" onclick="seleccionarTipo(3)">TIPO 3</button>
<button class="btn celeste" onclick="seleccionarTipo(4)">TIPO 4</button>
<button class="btn purple" onclick="seleccionarTipo(5)">TIPO 5</button>
<button class="btn gris" onclick="seleccionarTipo(6)">TIPO 6</button>
</div>
<div class="botonera-vertical">
<button class="btn green" id="btn-volver-tipos">REGRESAR</button>
</div>
<div id="tipo-configuracion" style="margin-top:20px; display:none;">
<h3 id="titulo-tipo"></h3>
<div id="parametros"></div>
<div class="botonera-tipo">
<button id="btn-add-param" class="btn azul">+</button>
<button class="btn verde" id="btn-guardar-tipo">Guardar</button>
<button class="btn rojo" id="btn-cancelar-tipo">Cancelar</button>
</div>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA DE AYUDA ---------------------------------------------------- -->
<div id="pantalla-ayuda" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>AYUDA</h2>
</div>
<hr>
<iframe src="/ayuda.html" style="width:100%; height:600px; border:none;"></iframe>
<div style="margin-top: 20px; text-align: center;">
<button class="btn rojo" data-action="salir-ayuda">SALIR</button>
</div>
</div>
</div>
<!-- ------------------------------------- MODAL PARA POPUP ---------------------------------------------------- -->
<div id="confirm-modal" class="modal">
<div class="modal-content">
<p id="modal-text">¿Deseas guardar los cambios?</p>
<div class="modal-buttons">
<button id="btn-modal-si" class="btn verde" onclick="confirmGuardar()"></button>
<button id="btn-modal-no" class="btn rojo" onclick="cancelarGuardar()">No</button>
</div>
</div>
</div>
</body>
</html>
+459
View File
@@ -0,0 +1,459 @@
/* global uibuilder */
uibuilder.start();
// Navegación entre pantallas
function mostrarPantalla(id) {
document.querySelectorAll('.pantalla').forEach(div =>
div.classList.toggle('visible', div.id === id)
);
}
let currentTopic = "";
let result = [];
//****************************************************RECEPCION DE MENSAJES DESDE NODE_RED ***************************************************************** */
uibuilder.onChange('msg', msg => {
if (msg.payload && msg.payload.pantalla) {
mostrarPantalla('pantalla-' + msg.payload.pantalla);
}
if (msg.semaforo) {
document.getElementById('estado').style.backgroundColor = msg.semaforo;
}
if (msg.payload && msg.payload.pantalla === 'snapshot') {
// Limpiar datos visibles
document.getElementById('camara').textContent = '---';
document.getElementById('placa').textContent = '----';
document.getElementById('fecha').textContent = '----';
// Mostrar imagen vacía
document.getElementById('foto').src = "";
}
if (msg.payload && msg.payload.pantalla === 'escuchar' && msg.buttons != true) {
// Limpiar datos visibles
document.getElementById('camara_escuchar').textContent = '---';
document.getElementById('placa_escuchar').textContent = '----';
document.getElementById('fecha_escuchar').textContent = '----';
document.getElementById('contador').textContent = `0 / 0`;
// Mostrar imagen vacía
document.getElementById('foto_escuchar').src = "";
}
if (msg.hasOwnProperty("picture")) {
//uibuilder.send({ payload: msg.payload.Picture.Plate.PlateNumber, topic: msg.payload.Picture.SnapInfo.DeviceID });
if (msg.snapshot_cam) {
document.getElementById('foto').src = msg.picture;
document.getElementById('placa').textContent = msg.plate || '----';
document.getElementById('fecha').textContent = msg.plate_time || '';
document.getElementById('camara').textContent = msg.camera_id || '---';
}
else if (msg.escuchar_cam){
document.getElementById('foto_escuchar').src = msg.picture;
document.getElementById('placa_escuchar').textContent = msg.plate || '----';
document.getElementById('fecha_escuchar').textContent = msg.plate_time || '';
document.getElementById('camara_escuchar').textContent = msg.camera_id || '---';
const total = msg.snapshot_indexmax || 0;
const actual = (msg.snapshot_readindex || 0) + 1;
document.getElementById('contador').textContent = `${actual} / ${total}`;
}
}
else if (msg.payload?.pantalla === 'tipos') {
mostrarPantalla('pantalla-tipos');
if (msg.payload.tiposDatos) {
tiposDatos = msg.payload.tiposDatos;
}
}
if (msg.barrera) {
mostrarBarreraAbierta()
}
else{
mostrarBarreraCerrada()
}
// tabla dinamica
if (msg.topic == "general" || msg.topic == "conexion" || msg.topic == "camara" || msg.topic == "sensores" || msg.topic == "mensajes" || msg.topic == "tiempos"){
if (!msg.tabla || !Array.isArray(msg.tabla)) return;
const tbody = document.getElementById('config-table-body');
tbody.innerHTML = '';
currentTopic = msg.topic || 'general';
msg.tabla.forEach(item => { //generamos dinamixamente la tabla que vera el usuario en el dispositivo (PC o smartphone).
// notese que la celda 2 de cada fila es del tipo editable.
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = item.parametro;
const cell2 = document.createElement('td');
cell2.contentEditable = true;
cell2.textContent = item.valor;
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row); // enviamos la tabla al elemento html (config-table-body)
});
}
// configuracion del pop up de notificaciones
if (msg.modalText) {
document.getElementById('modal-text').textContent = msg.modalText;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
if (msg.modalType === "notificacion") {
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
} else {
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = msg.modalCallback || null;
}
document.getElementById('confirm-modal').style.display = 'block';
}
// ======================================== Datos para la Pantalla Principal ========================================================================
if (msg.hasOwnProperty("peso")) document.getElementById('peso').textContent = msg.peso || 0;
//if (msg.semaforo_color) document.getElementById('semaforo-color').style.backgroundColor = msg.semaforo_color;
const led = document.getElementById('semaforo-color');
if (msg.semaforo_color) {
led.style.backgroundColor = msg.semaforo_color;
led.classList.add('led');
} else {
led.style.backgroundColor = '#ccc';
led.classList.remove('led');
}
if (msg.hasOwnProperty("matricula")) document.getElementById('matricula').textContent = msg.matricula;
if (msg.hasOwnProperty("peso_estable_flag")) document.getElementById('peso-estable').textContent = msg.peso_estable_flag ? 'SI' : 'NO';
if (msg.hasOwnProperty("vial")) document.getElementById('vial').textContent = msg.vial;
if (msg.hasOwnProperty("totem_id")) document.getElementById('totem-id').textContent = msg.totem_id;
if (msg.hasOwnProperty("iplan")) document.getElementById('iplan').textContent = msg.iplan;
if (msg.hasOwnProperty("ipsupervisor")) document.getElementById('ipsupervisor').textContent = msg.ipsupervisor;
});
//****************************************************************************************************************************************************** */
// Detectar clics en botones
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action]')) {
const action = ev.target.getAttribute('data-action');
console.log('⏺ Acción botón:', action);
uibuilder.send({ payload: { seccion: action } });
}
});
// Mostrar pantalla tipos
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action="tipos"]')) {
mostrarPantalla('pantalla-tipos');
}
});
document.getElementById('btn-add-param').addEventListener('click', agregarParametro);
document.getElementById('btn-guardar-tipo').addEventListener('click', () => {
window.modalCallback = guardarTipo;
document.getElementById('modal-text').textContent = `¿Deseas guardar los cambios de Tipo ${tipoActual}?`;
document.getElementById('confirm-modal').style.display = 'block';
});
document.getElementById('btn-cancelar-tipo').addEventListener('click', () => {
uibuilder.send({ payload: { accion: "cancelar_tipo", tipo: tipoActual } });
volverATipos();
});
document.getElementById('btn-volver-tipos').addEventListener('click', () => {
volverATipos(); // esto limpia la tabla con los parametros del Tipo en el que estemos
uibuilder.send({ payload: { seccion: "volver_tipos" } });
});
let tipoActual = null;
let tiposDatos = {}; // aquí se guarda la configuración temporal
//******************************************* FUNCIONES ******************************************************** */
// Acciones a partir de botones en html
function sendRow() {// Aqui llega cuando se hace click en el boton Actualizar de las tablas dinamicas
// recoge el contenido de la tabla en ese instante y saca Notificacion de guardar con opcion Si o No
const rows = document.querySelectorAll('#config-table-body tr');
result = []; // inicializamos result para poner exactamente la tabla actual
/*
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// normaliza a booleano si corresponde
if (valor === "true") valor = true;
if (valor === "false") valor = false;
result.push({ parametro, valor });
});
*/
//Modificacion de la lectura de la tabla para asegurar que los numeros son mantenidos como numeros y no como texto
// al igual que los valores booleanos.
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
if (/^(true|false)$/i.test(valor)) {
valor = valor.toLowerCase() === "true";
} else if (/^-?\d+$/.test(valor)) {
valor = parseInt(valor, 10);
} else if (/^-?\d+\.\d+$/.test(valor)) {
valor = parseFloat(valor);
}
result.push({ parametro, valor });
});
// Mostramos directamente la confirmación y definimos la callback. "topic" contiene el nombre de la tabla donde se guardan los valores que vienen en "result"
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({
payload: { table: result, seccion: "respuesta" },
topic: currentTopic,
origen: "actualizar"
});
});
}
function sendCancel() { // boton Cancelar de las tablas dinamicas
uibuilder.send({ payload: {seccion:'cancel-table'}, topic: currentTopic });
}
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
document.getElementById('confirm-modal').style.display = 'none';
if (typeof window.modalCallback === 'function') {
const cb = window.modalCallback;
window.modalCallback = null; // limpia para la siguiente vez
cb(); // ejecuta
} else if (typeof window.modalCallback === 'string') {
uibuilder.send({ payload: { table: result }, modalCallback: window.modalCallback, origen: "modal" });
}
}
/*
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
uibuilder.send({ payload: { table: result }, topic: currentTopic, origen: "modal-tabla" });
document.getElementById('confirm-modal').style.display = 'none';
}
*/
function cancelarGuardar() { // Respuesta Negativa del Modal de tabla dinamica
result = [];
document.getElementById('confirm-modal').style.display = 'none';
}
// Funciones para icono barrera
function mostrarBarreraAbierta() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_abierta.png";
/*
setTimeout(() => {
mostrarBarreraCerrada();
}, 1000); // 1 segundo
*/
}
function mostrarBarreraCerrada() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_cerrada.png";
}
//************************* FUNCIONES DE TIPOS DE CONFIGURACION *****************************/
function seleccionarTipo(n) {
tipoActual = n;
document.getElementById('titulo-tipo').textContent = `TIPO ${n}`;
document.getElementById('tipo-configuracion').style.display = 'block';
const contenedor = document.getElementById('parametros');
contenedor.innerHTML = '';
const datos = tiposDatos[n] || []; // ya es un array
datos.forEach(d => {
const row = crearFilaParametro(d.parametro, d.valor);
contenedor.appendChild(row);
});
}
function agregarParametro() {
const contenedor = document.getElementById('parametros');
if (contenedor.children.length >= 8) return;
const row = crearFilaParametro('', '');
contenedor.appendChild(row);
}
function crearFilaParametro(parametro, valor) {
const div = document.createElement('div');
div.className = 'parametro-row';
// Input para el nombre del parámetro
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
// Input para el valor: checkbox o texto
let inputValor;
if (valor === true || valor === false || valor === "true" || valor === "false") {
inputValor = document.createElement('input');
inputValor.type = 'checkbox';
inputValor.checked = (valor === true || valor === "true");
} else {
inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
}
inputParam.classList.add('parametro');
inputValor.classList.add('valor');
// Botón para eliminar la fila
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.flex = '0 0 40px';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.style.marginLeft = '4px';
btnDelete.style.zIndex = '1';
btnDelete.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
return false;
});
// Añadir los elementos a la fila
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function crearFilaParametro_viejo(parametro, valor) { // Crea un nuevo elemento en la tabla del Tipo que estemos trabajando con dos campos y un boton de borrado
const div = document.createElement('div');
div.className = 'parametro-row';
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
const inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.onclick = () => { // gestion de la alerta para el borrado, haciendo uso del modal que tenemos en html
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
};
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function guardarTipo() {
const contenedor = document.getElementById('parametros');
const filas = contenedor.querySelectorAll('.parametro-row');
const datos = [];
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
let valor;
if (val.type === 'checkbox') {
valor = val.checked;
} else {
valor = val.value.trim();
}
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor });
}
});
/*
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor: val.value.trim() });
}
});
*/
tiposDatos[tipoActual] = datos;
uibuilder.send({ payload: { tipo: tipoActual, datos }, topic: "tipoconfig" }); // el topic nos sirve para seleccionar en el proximo nodo la funcion de guardar los datos
volverATipos(); // 🔷 salimos de la pantalla del tipo actual
}
function volverATipos() {
document.getElementById('tipo-configuracion').style.display = 'none';
tipoActual = null;
}
//==========================================FUNCIONES PARA MANEJAR LAS NOTIFICACIONES ======================================================
function mostrarConfirmacion(texto, callback) {
document.getElementById('modal-text').textContent = texto;
// Restablece estado normal del modal
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = callback;
document.getElementById('confirm-modal').style.display = 'block';
}
function mostrarNotificacion(texto) {
document.getElementById('modal-text').textContent = texto;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
document.getElementById('confirm-modal').style.display = 'block';
}
/*
Cuando necesitemos un modal clásico (Sí/No), por ejemplo para confirmar actualización:
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({ payload: { table: result, seccion:"respuesta" }, topic: currentTopic, origen: "actualizar" });
});
Cuando necesitemos simplemente notificar un error:
mostrarNotificacion("La IP introducida es inválida, por favor corrígela.");
*/
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "ES2020",
"module": "ESNext",
"baseUrl": "./src",
"typeRoots": [
"./types"
]
},
"include": [
"types/**/*.d.ts",
"src/**/*.{js,cjs,mjs}"
]
}
+47
View File
@@ -0,0 +1,47 @@
/// <reference path="./uibuilder.module.d.ts" />
/**
* Make the uibuilder instance globally available (from uibuilder.module.d.ts)
* Also expose the other global helpers and object aliases from uibuilder
* @version 7.5.0
* Add the following to the top of any JS file to enable VS Code intellisense
* for uibuilder (adjust the path as needed):
* /// <reference path="../types/uibuilder.d.ts" />
*/
declare global {
// Match script-loaded global as a var on window and global scope
var uibuilder: import("./uibuilder.module").Uib
var uib: import("./uibuilder.module").Uib
var $: import("./uibuilder.module").Uib['$']
var $$: import("./uibuilder.module").Uib['$$']
var $ui: import("./uibuilder.module").Uib['$ui']
// Expose helpers mapped from uibuilder instance for script-loaded usage
// e.g. window['$'] = window['uibuilder'].$
interface Window {
uibuilder: import("./uibuilder.module").Uib
uib?: import("./uibuilder.module").Uib
$: import("./uibuilder.module").Uib['$']
$$: import("./uibuilder.module").Uib['$$']
$ui?: import("./uibuilder.module").Uib['$ui']
/** Alias of addEventListener for convenience */
on: Window['addEventListener']
}
/** Provide a global Document interface augmentation to match window.on */
interface Document {
/** Alias of addEventListener for convenience */
on: Document['addEventListener']
}
/** Add Element.prototype aliases for common DOM helpers */
interface Element {
/** Alias of querySelector */
query: Element['querySelector']
/** Alias of querySelectorAll */
queryAll: Element['querySelectorAll']
/** Alias of addEventListener */
on: Element['addEventListener']
}
}
export {};
+774
View File
@@ -0,0 +1,774 @@
/**
* Type definitions for uibuilder.module.js
* WCAG 2.2 AA, ESLint v9, Shift-Left security, and project conventions applied.
* @version 7.5.0
* @author Julian Knight (Totally Information)
*/
export type HtmlString = string
/** Column metadata for tables */
export interface ColumnDefinition {
index: number,
hasName: boolean,
title: string,
name?: string,
key?: string | number,
dataType?: 'string' | 'date' | 'number' | 'html',
editable?: boolean,
}
/** Options for building HTML tables */
export interface TableOptions {
cols?: ColumnDefinition[],
parent?: HTMLElement | string,
allowHTML?: boolean,
}
/** Options for tblAddListener */
export interface TableListenerOptions {
eventScope?: 'row' | 'cell',
returnType?: 'text' | 'html',
pad?: number,
send?: boolean,
logLevel?: string | number,
eventType?: string,
}
/** Options for notification */
export interface NotificationConfig {
title?: string,
body?: string,
return?: boolean,
[key: string]: any,
}
/** Options for showOverlay */
export interface OverlayOptions {
content?: string,
title?: string,
icon?: string,
type?: 'success' | 'info' | 'warning' | 'error',
showDismiss?: boolean,
autoClose?: number | null,
time?: boolean,
}
/**
* Uibuilder main class
* @typicalname uibuilder
* @description The client-side Front-End JavaScript for uibuilder in HTML Module form.
* Provides a number of global objects that can be used in your own JavaScript.
* See the docs folder `./docs/uibuilder.module.md` for details of how to use this fully.
* @version 7.5.0
* @author Julian Knight (Totally Information)
*/
export class Uib {
/**
* Static metadata for the Uibuilder client
*/
static _meta: {
version: string,
type: string,
displayName: string,
}
/** Client ID set by uibuilder on connect */
clientId: string
/** The collection of cookies provided by uibuilder */
cookies: Record<string, string>
/** Copy of last control msg object received from server */
ctrlMsg: object
/** Is Socket.IO client connected to the server? */
ioConnected: boolean
/** Is the library running from a minified version? */
isMinified: boolean
/** Is the browser tab containing this page visible or not? */
isVisible: boolean
/** Remember the last page (re)load/navigation type: navigate, reload, back_forward, prerender */
lastNavType: string
/** Max msg size that can be sent over Socket.IO - updated by "client connect" msg receipt */
maxHttpBufferSize: number
/** Last std msg received from Node-RED */
msg: object
/** Number of messages sent to server since page load */
msgsSent: number
/** Number of messages received from server since page load */
msgsReceived: number
/** Number of control messages sent to server since page load */
msgsSentCtrl: number
/** Number of control messages received from server since page load */
msgsCtrlReceived: number
/** Is the client online or offline? */
online: boolean
/** Last control msg object sent via uibuilder.send() */
sentCtrlMsg: object
/** Last std msg object sent via uibuilder.send() */
sentMsg: object
/** Placeholder to track time offset from server, see fn socket.on(ioChannels.server ...) */
serverTimeOffset: number | null
/** Placeholder for a socket error message */
socketError: string | null
/** Tab identifier from session storage */
tabId: string
/** Actual name of current page (set in constructor) */
pageName: string | null
/** Is the DOMPurify library loaded? Updated in start() */
purify: boolean
/** Is the Markdown-IT library loaded? Updated in start() */
markdown: boolean
/** Current URL hash. Initial set is done from start->watchHashChanges via a set to make it watched */
urlHash: string
/** Default originator node id - empty string by default */
originator: string
/** Optional default topic to be included in outgoing standard messages */
topic?: string
/** Either undefined or a reference to a uib router instance. Set by uibrouter, do not set manually. */
uibrouterinstance?: any
/** Set by uibrouter, do not set manually */
uibrouter_CurrentRoute?: any
/** Internal: auto-send ready flag */
autoSendReady: boolean
/** Node-RED setting (via cookie) */
httpNodeRoot: string
/** Socket.IO namespace - unique to each uibuilder node instance */
ioNamespace: string
/** Socket.IO path */
ioPath: string
/** Starting delay factor for subsequent reconnect attempts */
retryFactor: number
/** Starting retry ms period for manual socket reconnections workaround */
retryMs: number
/** Prefix for all uib-related localStorage */
storePrefix: string
/** Whether uibuilder client has started */
started: boolean
/** Socket.IO connection options */
socketOptions: object
/** How many times has the loaded instance connected to Socket.IO */
connectedNum: number
/** Is Vue available? */
isVue: boolean
/** Vue version if available */
vueVersion?: string
/** Current transport being used by Socket.IO */
currentTransport?: string
/** Last msg received from global Socket.IO namespace */
globalMsg?: any
/** List of uib specific attributes that will be watched and processed dynamically */
uibAttribs: string[]
/** The URL fragment identifier for the current uib instance */
url?: string
// --- Getters/Setters ---
logLevel: number
meta: typeof Uib._meta
/**
* Set uibuilder properties to a new value - works on any property except _* or #*
* Also triggers any event listeners.
* @param prop Any uibuilder property who's name does not start with a _ or #
* @param val The set value of the property or a string declaring that a protected property cannot be changed
* @param store If true, the variable is also saved to the browser localStorage if possible
* @param autoload If true & store is true, on load, uib will try to restore the value from the store automatically
* @returns Input value
*/
set(prop: string, val: any, store?: boolean, autoload?: boolean): any
/**
* Get the value of a uibuilder property
* @param prop The name of the property to get as long as it does not start with a _ or #
* @returns The current value of the property
*/
get(prop: string): any
/**
* Write to localStorage if possible. Console error output if can't write
* Also uses this.storePrefix
* @param id localStorage var name to be used (prefixed with 'uib_')
* @param value value to write to localstore
* @param autoload If true, on load, uib will try to restore the value from the store
* @returns True if succeeded else false
*/
setStore(id: string, value: any, autoload?: boolean): boolean
/**
* Attempt to get and re-hydrate a key value from localStorage
* @param id The key of the value to attempt to retrieve
* @returns The re-hydrated value of the key or null if key not found, undefined on error
*/
getStore(id: string): any
/**
* Remove a given id from the uib keys in localStorage
* @param id The key to remove
*/
removeStore(id: string): void
/**
* Returns a list of uibuilder properties (variables) that can be watched with onChange
* @returns List of uibuilder managed variables
*/
getManagedVarList(): Record<string, string>
/**
* Returns a list of currently watched variables
* @returns List of watched variable names
*/
getWatchedVars(): string[]
/**
* Register on-change event listeners for uibuilder tracked properties
* @param prop The property of uibuilder that we want to monitor
* @param callback The function that will run when the property changes, parameter is the new value of the property after change
* @returns A reference to the callback to cancel
*/
onChange(prop: string, callback: (val: any) => void): number
/**
* Cancel a previously registered onChange event listener
* @param prop The property name
* @param cbRef The callback reference number
*/
cancelChange(prop: string, cbRef: number): void
/**
* Register a change callback for a specific msg.topic
* @param topic The msg.topic we want to listen for
* @param callback The function that will run when an appropriate msg is received
* @returns A reference to the callback to cancel
*/
onTopic(topic: string, callback: (msg: any) => void): number
/**
* Cancel a previously registered onTopic event listener
* @param topic The topic name
* @param cbRef The callback reference number
*/
cancelTopic(topic: string, cbRef: number): void
/**
* Returns a new array containing the intersection of the 2 input arrays
* @param a1 Array to check
* @param a2 Array to intersect
* @returns The intersection of the 2 arrays (may be an empty array)
*/
arrayIntersect<T>(a1: T[], a2: T[]): T[]
/**
* Copies a uibuilder variable to the browser clipboard
* @param varToCopy The name of the uibuilder variable to copy to the clipboard
*/
copyToClipboard(varToCopy: string): void
/**
* Does the chosen CSS Selector currently exist?
* @param cssSelector Required. CSS Selector to examine for visibility
* @param msg Optional, default=true. If true also sends a message back to Node-RED
* @returns True if the element exists
*/
elementExists(cssSelector: string, msg?: boolean): boolean
/**
* Format a number using the INTL standard library
* @param value Number to format
* @param decimalPlaces Number of decimal places to include
* @param intl standard locale spec, e.g. "ja-JP" or "en-GB"
* @param opts INTL library options object
* @returns formatted number
*/
formatNumber(value: number, decimalPlaces?: number, intl?: string, opts?: object): string
/**
* Attempt to get rough size of an object
* @param obj Any serialisable object
* @returns Rough size of object in bytes or undefined
*/
getObjectSize(obj: any): number | undefined
/**
* Returns true if a uibrouter instance is loaded, otherwise returns false
* @returns true if uibrouter instance loaded else false
*/
hasUibRouter(): boolean
/**
* Only keep the URL Hash & ignoring query params
* @param url URL to extract the hash from
* @returns Just the route id
*/
keepHashFromUrl(url: string): string
/**
* Custom logging function
* @param args Arguments to log
*/
log(...args: any[]): void
/**
* Makes a null or non-object into an object. If thing is already an object.
* If not null, moves "thing" to {payload:thing}
* @param thing Thing to check
* @param property property that "thing" is moved to if not null and not an object. Default='payload'
* @returns Object
*/
makeMeAnObject(thing: any, property?: string): object
/**
* Navigate to a new page or a new route (hash)
* @param url URL to navigate to. Can be absolute or relative (to current page) or just a hash for a route change
* @returns The new window.location string
*/
navigate(url: string): Location
/**
* Scroll the page or a specific element into view
* @param cssSelector Optional. If not set, scrolls to top of page. Can also be 'top'|'start'|'bottom'|'end'
* @param opts Optional DOM scrollIntoView options
* @returns True if element was found (or top/bottom handled), false otherwise
*/
scrollTo(cssSelector?: string, opts?: { block?: string, inline?: string, behavior?: string }): boolean
/**
* Convert a string attribute into a variable/constant reference
* Used to resolve data sources in attributes
* @param path The string path to resolve, must be relative to the `window` global scope
* @returns The resolved data source or null
*/
resolveDataSource(path: string): any
/**
* Fast but accurate number rounding
* @param num The number to be rounded
* @param decimalPlaces Number of DP's to round to
* @returns Rounded number
*/
round(num: number, decimalPlaces: number): number
/**
* Set the default originator. Set to '' to ignore. Used with uib-sender.
* @param originator A Node-RED node ID to return the message to
*/
setOriginator(originator?: string): void
/**
* HTTP Ping/Keep-alive - makes a call back to uibuilder's ExpressJS server and receives a 204 response
* Can be used to keep sessions alive.
* @param ms Repeat interval in ms
*/
setPing(ms?: number): void
/**
* Convert JSON to Syntax Highlighted HTML
* @param json A JSON/JavaScript Object
* @returns Object reformatted as highlighted HTML
*/
syntaxHighlight(json: object): HtmlString
/**
* Returns true/false or a default value for truthy/falsy and other values
* @param val The value to test
* @param deflt Default value to use if the value is not truthy/falsy
* @returns The truth! Or the default
*/
truthy(val: any, deflt: any): boolean | any
/**
* Joins all arguments as a URL string
* @param paths URL fragments
* @returns Joined URL string
*/
urlJoin(...paths: string[]): string
/**
* Turn on/off/toggle sending URL hash changes back to Node-RED
* @param toggle Optional on/off/etc
* @returns True if we will send a msg to Node-RED on a hash change
*/
watchUrlHash(toggle?: any): boolean
/**
* DEPRECATED FOR NOW - wasn't working properly.
* Is the chosen CSS Selector currently visible to the user? NB: Only finds the FIRST element of the selection.
* @returns False
*/
elementIsVisible(): false
// --- UI handlers ---
/**
* Simplistic jQuery-like document CSS query selector, returns an HTML Element.
* If the selected element is a <template>, returns the first child element.
* @param cssSelector A CSS Selector that identifies the element to return
* @returns Selected HTML element or null
*/
$: (cssSelector: string) => HTMLElement | null
/**
* CSS query selector that returns ALL found selections as an array of elements.
* @param cssSelector A CSS Selector that identifies the elements to return
* @returns Array of DOM elements/nodes. Array is empty if selector is not found.
*/
$$: (cssSelector: string) => HTMLElement[]
/**
* Reference to the full ui library
*/
$ui: any
/**
* Add one or several class names to an element
* @param classNames Single or array of classnames
* @param el HTML Element to add class(es) to
*/
addClass(classNames: string | string[], el: HTMLElement): void
/**
* Apply a source template tag to a target html element
* @param source The source element
* @param target The target element
* @param onceOnly If true, the source will be adopted (the source is moved)
*/
applyTemplate(source: HTMLElement, target: HTMLElement, onceOnly: boolean): void
/**
* Builds an HTML table from an array (or object) of objects
* @param data Input data array or object
* @param opts Table options
* @returns Output HTML Element
*/
buildHtmlTable(data: object[] | object, opts?: TableOptions): HTMLTableElement | HTMLParagraphElement
/**
* Directly add a table to a parent element.
* @param data Input data array or object
* @param opts Build options
*/
createTable(data?: object[] | any[], opts?: TableOptions): void
/**
* Converts markdown text input to HTML if the Markdown-IT library is loaded
* Otherwise simply returns the text
* @param mdText The input markdown string
* @returns HTML (if Markdown-IT library loaded and parse successful) or original text
*/
convertMarkdown(mdText: string): string
/**
* ASYNC: Include HTML fragment, img, video, text, json, form data, pdf or anything else from an external file or API
* @param url The URL of the source file to include
* @param uiOptions Object containing properties recognised by the _uiReplace function. Must at least contain an id
*/
include(url: string, uiOptions: object): Promise<void>
/**
* Attach a new remote script to the end of HEAD synchronously
* @param url The url to be used in the script src attribute
*/
loadScriptSrc(url: string): void
/**
* Attach a new remote stylesheet link to the end of HEAD synchronously
* @param url The url to be used in the style link href attribute
*/
loadStyleSrc(url: string): void
/**
* Attach a new text script to the end of HEAD synchronously
* @param textFn The text to be loaded as a script
*/
loadScriptTxt(textFn: string): void
/**
* Attach a new text stylesheet to the end of HEAD synchronously
* @param textFn The text to be loaded as a stylesheet
*/
loadStyleTxt(textFn: string): void
/**
* Load a dynamic UI from a JSON web response
* @param url URL that will return the ui JSON
*/
loadui(url: string): void
/**
* Remove All, 1 or more class names from an element
* @param classNames Single or array of classnames. If undefined, "" or null, remove all classes
* @param el HTML Element to remove class(es) from
*/
removeClass(classNames: string | string[] | undefined | null, el: HTMLElement): void
/**
* Replace or add an HTML element's slot from text or an HTML string
* WARNING: Executes <script> tags! And will process <style> tags.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param slot The slot content we are trying to add/replace (defaults to empty string)
*/
replaceSlot(el: Element, slot: any): void
/**
* Replace or add an HTML element's slot from a Markdown string
* Only does something if the markdownit library has been loaded to window.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param component The component we are trying to add/replace
*/
replaceSlotMarkdown(el: Element, component: any): void
/**
* Sanitise HTML to make it safe - if the DOMPurify library is loaded
* Otherwise just returns that HTML as-is.
* @param html The input HTML string
* @returns The sanitised HTML or the original if DOMPurify not loaded
*/
sanitiseHTML(html: string): string
/**
* Creates and displays an overlay window with customizable content and behavior
* @param options Configuration options for the overlay
* @returns Object with close() method to manually close the overlay
*/
showOverlay(options: OverlayOptions): { close: () => void }
/**
* Add table event listener that returns the text or html content of either the full row or a single cell
* @param tblSelector The table CSS Selector
* @param options Additional options
* @param out A variable reference that will be updated with the output data upon a click event
*/
tblAddListener(tblSelector: string, options?: TableListenerOptions, out?: object): void
/**
* Add a row to a table element in the DOM.
* @param tbl The table element or selector to add the row to
* @param rowData The data for the new row (object or array)
* @param options Optional configuration for row creation
* @returns The created HTMLTableRowElement
*/
tblAddRow(tbl: string | HTMLTableElement, rowData: object | any[], options?: object): HTMLTableRowElement
/**
* Remove a row from a table element in the DOM.
* @param tbl The table element or selector to remove the row from
* @param rowIndex The index of the row to remove
* @param options Optional configuration for row removal
*/
tblRemoveRow(tbl: string | HTMLTableElement, rowIndex: number, options?: object): void
/**
* Show a dialog (notification or alert) in the UI.
* @param type The dialog type: 'notify' or 'alert'
* @param ui The UI configuration object for the dialog
* @param msg Optional message object to include
*/
showDialog(type: 'notify' | 'alert', ui: object, msg?: object): void
/**
* Apply a UI definition (JSON) to the current page.
* @param json The UI definition object
*/
ui(json: object): void
/**
* Get properties or values from UI elements matching a selector.
* @param cssSelector The CSS selector for the elements
* @param propName Optional property name to retrieve
* @returns Array of property values or elements
*/
uiGet(cssSelector: string, propName?: string): any[]
/**
* Enhance a DOM element with a UI component definition.
* @param el The element to enhance
* @param component The component definition or configuration
*/
uiEnhanceElement(el: any, component: any): void
// --- DOM/HTML cache ---
/**
* Clear the cached HTML content from memory or storage.
*/
clearHtmlCache(): void
/**
* Restore HTML content from the cache into the DOM.
*/
restoreHtmlFromCache(): void
/**
* Save the current HTML content to the cache for later restoration.
*/
saveHtmlCache(): void
// --- Message Handling ---
/**
* Send a standard message to Node-RED via Socket.IO.
* @param msg The message object to send
* @param originator Optional Node-RED node ID to return the message to
*/
send(msg: object, originator?: string): void
/**
* Easily send a msg back to Node-RED on a DOM event
* @param domevent DOM Event object
* @param originator A Node-RED node ID to return the message to
*/
eventSend(domevent: Event, originator?: string): void
/**
* Send a message to a specific room via Socket.IO.
* @param room The room name
* @param msg The message to send
*/
sendRoom(room: string, msg: any): void
/**
* Join a Socket.IO room.
* @param room The room name to join
*/
joinRoom(room: string): void
/**
* Leave a Socket.IO room.
* @param room The room name to leave
*/
leaveRoom(room: string): void
/**
* Send a control message to Node-RED via Socket.IO.
* @param msg The control message object to send
*/
sendCtrl(msg: object): void
/**
* Send a custom message on a specific channel via Socket.IO.
* @param channel The custom channel name
* @param msg The message object to send
*/
sendCustom(channel: string, msg: object): void
/**
* Upload a file to the server via Socket.IO.
* @param file The file to upload
* @param meta Optional metadata to send with the file
*/
uploadFile(file: File, meta?: object): void
// --- Socket.IO ---
/**
* Connect the Socket.IO client to the server.
*/
connect(): void
/**
* Disconnect the Socket.IO client from the server.
*/
disconnect(): void
// --- Startup ---
/**
* Start the uibuilder client, initializing all features and connections.
* @param options Optional startup options
*/
start(options?: object): void
// --- Show/hide ---
/**
* Show or hide the message area in the UI.
* @param showHide If true, show the message area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the message area is shown, false if hidden
*/
showMsg(showHide?: boolean, parent?: string): boolean
/**
* Show or hide the status area in the UI.
* @param showHide If true, show the status area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the status area is shown, false if hidden
*/
showStatus(showHide?: boolean, parent?: string): boolean
// --- Watchers ---
/**
* Watch a DOM element for changes and optionally send updates to Node-RED.
* @param cssSelector The CSS selector to watch
* @param startStop Start, stop, or toggle the watcher
* @param send If true, send updates to Node-RED
* @param showLog If true, log watcher activity
* @returns True if watching, false otherwise
*/
uiWatch(cssSelector: string, startStop?: boolean | 'toggle', send?: boolean, showLog?: boolean): boolean
/**
* Watch the DOM for changes (e.g., for dynamic UI updates).
* @param startStop Start or stop watching
*/
watchDom(startStop: boolean): void
// --- Notifications ---
/**
* Show a notification or alert in the UI.
* @param config Notification configuration or string message
* @returns A promise resolving to the notification event, or null
*/
notify(config: NotificationConfig | string): Promise<Event> | null
/**
* Wrap a provided variable in a proxy object so that it can be used reactively
* @param srcvar The source variable to wrap
* @returns A proxy object that can be used reactively
*/
reactive(srcvar: any): any
/**
* Get the Reactive class for advanced usage
* @returns The Reactive class constructor
*/
getReactiveClass(): any
/**
* Send log text to uibuilder's beacon endpoint (works even if socket.io not connected)
* @param txtToSend Text string to send
* @param logLevel Log level to use. If not supplied, will default to debug
*/
beaconLog(txtToSend: string, logLevel?: string): void
/**
* Request the current page's metadata from the server - response is handled automatically
*/
getPageMeta(): void
/**
* Easily send the entire DOM/HTML msg back to Node-RED
* @param originator A Node-RED node ID to return the message to
* @param send If true (default) directly send response to Node-RED
* @returns The HTML as a string
*/
htmlSend(originator?: string, send?: boolean): string
/**
* Send log info back to Node-RED over uibuilder's websocket control output (Port #2)
* @param args All arguments passed to the function are added to the msg.payload
*/
logToServer(...args: any[]): void
/**
* Get or create a (hopefully) unique ID
* @param el Source form element
* @returns A hopefully unique element ID
*/
returnElementId(el: HTMLElement): string | null
/**
* Attempt to get target attributes - can fail for certain target types, if so, returns empty object
* @param el Target element
* @returns Array of key/value HTML attribute objects
*/
getElementAttributes(el: HTMLElement): object
/**
* Check for CSS Classes and return as array if found or undefined if not
* @param el Target element
* @returns Array of class names
*/
getElementClasses(el: HTMLElement): string[] | undefined
/**
* Get target custom properties - only shows custom props not element default ones
* @param el Target element
* @returns Object of propname/value pairs
*/
getElementCustomProps(el: HTMLElement): object
/**
* Check for el.value and el.checked
* @param el HTML Element to be checked
* @returns Return null if properties not present, else the appropriate value
*/
getFormElementValue(el: HTMLElement): { value: any, checked: boolean | null }
/**
* For HTML Form elements, return the details
* @param el Source form element
* @returns Form element key details
*/
getFormElementDetails(el: HTMLFormElement): object | null
// --- Clipboard ---
/**
* Copy a uibuilder variable's value to the clipboard.
* @param varToCopy The name of the uibuilder variable to copy
*/
copyToClipboard(varToCopy: string): void
}
/** The default uibuilder instance */
declare const uibuilder: Uib
export { uibuilder }
export default uibuilder
+178
View File
@@ -0,0 +1,178 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+77
View File
@@ -0,0 +1,77 @@
# uibuilder Template: Blank (Default)
> NOTE: You can replace the contents of this README with text that describes your UI.
This is about the simplest template you can get for uibuilder. Is is also (as of uibuilder v5+), the default template.
It does not use any frameworks and has no other dependencies. It demonstrates that you can use uibuilder purely with HTML/JavaScript or even just HTML and still easily build a simple, dynamic, data-driven user interface with the help of Node-RED.
All it does is load the uibuilder client library and connect to Node-RED.
## UI
Initially only shows an H1 heading with a sub-heading. However, it contains a `<div>` with the id "`more`" which is used by many of the examples in the Node-RED import library.
In addition, the `more` div uses uibuilder's `uib-topic` special attribute which allows it to be used as a target for messages sent from Node-RED. This is a useful feature that allows you to easily update the content of the page without having to write any JavaScript code. Send a message containing `{ topic: 'more', payload: 'Hello World' }` to the `uibuilder` node and the content of the `more` div will be updated with "Hello World". Note that the payload can contain HTML. As an example, use an inject node with `msg.payload` set to use a JSONata expression like `"<b style='background-color:var(--error)'>Hello!</b> This is a message from Node-RED at " & $moment()`. Don't forget to set `msg.topic` to `more` so that the uibuilder client library knows where to send the message.
> **WARNING**: Using the "more" topic completely overwrites the contents of the `more` div.
## Folders
* `/` - The root folder contains this file. It can be used for other things **but** it will not be served up in the Node-RED web server.
* `/src/` - the default folder that serves files as web resources. However, this can be changed to a different folder if desired.
* `/dist/` - the default folder for serving files as web resources where a build step is used. In that case, the `/src` folder is the source used by the build tool and `/dist` is the destination for the build (the "distribution" folder).
* `/routes/` - This folder can contain `.js` files defining routing middleware for uibuilder's ExpressJS web server.
* `/api/` - This folder can contain `.js` files defining REST API's specific to this uibuilder instance.
* `/types/` - Contains typescript definition files (`*.d.ts`) for the uibuilder client library. This is not used by uibuilder but can be used by your IDE to provide type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Remember to update these for new uibuilder versions.
The above folders will all pre-exist for the built-in uibuilder templates. The folders can safely be removed if not needed but one folder must exist to serve the web resources from (this cannot be the root folder).
The template only has files in the root and `src` folders. The `src` folder is the default used by uibuilder to serve up files to clients.
One reserved item in the root folder however will be a `package.json` file. This will be used in the future to help with build/compile steps. You can still use it yourself, just bear in mind that a future version of uibuilder will make use it as well. If you need to have any development packages installed to build your UI, don't forget to tell `npm` to save them as development dependencies not normal dependencies.
The `dist` folder should be used if you have a build step to convert your source code to something that browsers understand. So if you are using a build (compile) step to produce your production code, ensure that it is configured to use the `dist` folder as the output folder and that it creates at least an `index.html` file.
You can switch between the `src` and `dist` (or other) folders using the matching setting in the Editor. See uibuilder's advanced settings tab.
Also note that you can use **linked** folders and files in this folder structure. This can be handy if you want to maintain your code in a different folder somewhere or if your default build process needs to use sub-folders other than `src` and `dist`.(Though as of v6, you can specify any sub-folder to be served)
## Files in this template
* `package.json`: REQUIRED. Defines the basic structure, name, description of the project and defines any local development dependencies if any. Also works with `npm` allowing the installation of dev packages (such as build or linting tools).
* `README.md`: This file. Change this to describe your web app and provide documentation for it.
* `eslint.config.js`: A pre-configured configuration for the ESLINT tool. Helps when writing front-end code. Note that you need at least eslint v8+ installed for this to work.
* `LICENSE`: A copy of the Apache 2.0 license. Replace with a different license if needed. Always license your code. Apache 2.0 matches the licensing of uibuilder.
* `src/index.html`: REQUIRED. Contains your basic HTML and will be the file loaded and displayed in the browser when going to the uibuilder defined URL.
* `src/index.js`: Contains all of the logic for your UI. It must be linked to in the html file. Optional.
* `src/index.css`: Contains your custom CSS for styling. It must be linked to in the html file. Optional.
* `tsconfig.json`: A configuration file for TypeScript. This can be used by your IDE to provide descriptions, type checking and auto-completion for the uibuilder client library. This is useful if you are using TypeScript or JavaScript with type checking enabled. Uses the typescript definition files in the `/types` folder, remember to update these for new uibuilder versions.
Note that only the `package.json` and `index.html` files are actually _required_. uibuilder will not function as expected without them.
It is possible to use the index.html file simply as a link to other files but it must be present.
The other files are all optional. However, you will need to change the index.html file accordingly if you rename or remove them.
## Multiple HTML pages
uibuilder will happily serve up any number of web pages from a single instance. It will also make use of sub-folders. However, each folder should have an `index.html` file so that a URL that ends with the folder name will still work without error.
Note that each html file is a separate page and requires its own JavaScript and uibuilder library reference. When moving between pages, remember that every page is stand-alone, a new environment. You can share one `index.js` file between multiple pages if you prefer but each page will run a separate instance.
If multiple pages are connected to the same uibuilder instance, they will all get the same broadcast messages from Node-RED. So if you want to handle different messages on different pages, remember to filter them in your front-end JavaScript in `uibuilder.onChange('msg', ....)` function. Turn on the advanced flag for including a `msg._uib` property in output if you need to differentiate between pages and/or clients in Node-RED.
## URL endpoints
When specifying links in your HTML, CSS and JavaScript files, you should use relative URLs. e.g. `./index.mjs` will load that file from the `src` folder or wherever else you have told uibuilder to use.
When using uibuilder's server-side resources, you will generally use `../uibuilder/....`, for example `../uibuilder/uib-brand.min.css` as seen in the default `index.css` file. When accessing a front-end library being served by uibuilder, you can use the form `../uibuilder/vendor/....`. Use the "Full details" button in the uibuilder node to see all of the possible endpoints you may want to use.
## License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
This template may be used however you like. It is provided as a test template for uibuilder and is not intended to be a full template. You are free to use it as a starting point for your own template or to use it as-is if you find it useful.
View File
View File
+121
View File
@@ -0,0 +1,121 @@
import { defineConfig } from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'
import jsdoc from 'eslint-plugin-jsdoc'
import stylistic from '@stylistic/eslint-plugin'
import html from 'eslint-plugin-html'
// Shared rules
const jsdocRules = {
'jsdoc/check-alignment': 'off',
// "jsdoc/check-indentation": ["warn", {"excludeTags":['example', 'description']}],
'jsdoc/check-indentation': 'off',
'jsdoc/check-param-names': 'warn',
'jsdoc/check-tag-names': ['warn', {
definedTags: ['typicalname', 'element', 'memberOf', 'slot', 'csspart'],
}],
'jsdoc/multiline-blocks': ['error', {
noZeroLineText: false,
}],
'jsdoc/no-multi-asterisk': 'off',
'jsdoc/no-undefined-types': ['error', {
definedTypes: ['JQuery', 'NodeListOf', 'ProxyHandler'],
}],
'jsdoc/tag-lines': 'off',
}
const stylisticRules = {
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true, }],
'@stylistic/comma-dangle': ['error', {
arrays: 'only-multiline',
objects: 'always',
imports: 'never',
exports: 'always-multiline',
functions: 'never',
importAttributes: 'never',
dynamicImports: 'never',
}],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/indent': ['error', 4, {
SwitchCase: 1,
}],
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/linebreak-style': ['error', 'unix'],
'@stylistic/lines-between-class-members': 'off',
'@stylistic/newline-per-chained-call': ['error', {
ignoreChainWithDepth: 2,
}],
'@stylistic/no-confusing-arrow': 'error',
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/semi': ['error', 'never'],
'@stylistic/space-before-function-paren': 'off',
'@stylistic/spaced-comment': ['error', 'always', {
line: {
exceptions: ['*', '#region', '#endregion'],
},
block: {
exceptions: ['*'],
},
}],
'@stylistic/space-in-parens': 'off',
'@stylistic/quotes': ['error', 'single', {
avoidEscape: true,
allowTemplateLiterals: 'always',
}],
}
const generalRules = {
'new-cap': 'error',
'no-else-return': 'error',
'no-empty': ['error', {
allowEmptyCatch: true,
}],
'no-unused-vars': 'off',
'no-useless-escape': 'off',
'no-var': 'warn',
'prefer-const': 'error',
}
export default defineConfig([
// Apply to all JavaScript files
{
files: ['**/*.js', '**/*.html'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'script', // Use script rather than ES modules
globals: {
...globals.browser,
// ...globals.node,
UibRouter: 'readonly',
uibuilder: 'readonly',
$: 'readonly',
$$: 'readonly',
},
},
plugins: {
'js': js,
'jsdoc': jsdoc,
'@stylistic': stylistic,
'html': html,
},
extends: [
js.configs.recommended,
jsdoc.configs['flat/recommended'],
stylistic.configs.recommended,
],
rules: {
...jsdocRules,
...stylisticRules,
...generalRules,
// 'no-empty': ['error', { 'allowEmptyCatch': true }],
},
},
// Specific rules for configuration files
{
files: ['eslint.config.mjs', '**/*.config.js', '**/*.config.mjs'],
languageOptions: {
sourceType: 'module', // Config files can use ES modules
},
},
])
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uib-blank",
"version": "2025-05-27",
"private": true,
"description": "This is about the simplest template you can get for uibuilder.",
"browser": "./src/index.js",
"scripts": {
"build": "echo \"No build process specified\""
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@stylistic/eslint-plugin": "^4.2.0",
"eslint": "^9.27.0",
"eslint-plugin-html": "^8.1.3",
"eslint-plugin-jsdoc": "^50.6.17",
"globals": "^16.1.0"
},
"keywords": ["uibuilder", "node-red", "node-red-contrib-uibuilder"],
"author": "Julian Knight (Totally Information)",
"license": "Apache-2.0",
"homepage": "https://github.com/TotallyInformation/node-red-contrib-uibuilder",
"bugs": "https://github.com/TotallyInformation/node-red-contrib-uibuilder/issues",
"repository": {
"type": "git",
"url": "https://github.com/TotallyInformation/node-red-contrib-uibuilder.git"
},
"browserslist": [
"> 0.5%",
"maintained versions",
"last 2 versions",
"not dead",
"not ie > 0"
]
}
View File
+110
View File
@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pantalla de Ayuda</title>
<style>
body { font-family: Arial, sans-serif; }
.container { max-width: 800px; margin: auto; padding: 20px; }
</style>
</head>
<body>
<!-- -------------------------------------- PANTALLA DE AYUDA --------------------------------------------------- -->
<div id="pantalla-ayuda" class="pantalla">
<div class="container">
<div class="header">
<h2>Trypton Software</h2>
</div>
<h3>AYUDA - PARÁMETROS CONFIGURABLES</h3>
<hr>
<div style="max-height: 70vh; overflow-y: auto; text-align: justify; font-size: 14px; padding: 0 5px;">
<p>Totem</p>
<p>AYUDA EN LA DEFINICIÓN DE PARÁMETROS CONFIGURABLES</p>
<p>Menu Configuración GENERAL</p>
<p><strong>Tipo_Configuracion</strong> = 4. Indica el TIPO de configuración que debemos aplicar durante la configuración. Los TIPOS se pueden definir en el menu de configuración(Config/Editar Configuracion/Tipos de Configuración), el usuario puede definir hasta ocho parámetros en cada TIPO. El usuario debe definir los parámetros y sus valores los cuales durante la carga del archivo de configuración se tomaran en cuenta por encima de lo que digan los parámetros de configuracion que hayamos definido en los botones GENERAL, CAMARA, SENSORES… Si no queremos aplicar ningún TIPO entonces colocamos Tipo_Configuracion = 0.</p>
<p><strong>ID_obligatorio</strong>: Si true, indica que es obligatorio mostrar una identificación autorizada (RFID / QR) para poder continuar con el proceso de pesaje.</p>
<p><strong>detector_obligatorio</strong>: Si true, indica que aunque el vehículo haya sido detectado frente al totem, por otro medio (QR, Botón,...) es obligatorio que el vehículo sea visto por el detector, de otra manera el proceso queda detenido.</p>
<p><strong>boton_obligatorio</strong>: Si true, indica que el conductor debe hacer click en el botón de salida que se presenta en la pantalla para que el proceso de pesada pueda finalizar, de otra manera el proceso queda detenido.</p>
<p><strong>firma_obligatoria</strong>: Si true, el Totem solicitara por pantalla la firma del conductor una vez que se haya detectado el peso estable en la bascula.</p>
<p><strong>identificacion_esp</strong>: Si true, indica que en caso que la matrícula no haya podido se leida, el vehículo puede continuar hasta el totem pero debe presentar un QR o una tarjeta RFID autorizados.</p>
<p><strong>edicion_matricula</strong>: Si true, indica que en caso de lectura erronea de la matrícula el Totem presentara un editor de matrícula para quie el conductor pueda editar la matrícula.</p>
<p><strong>botonsalida_activo</strong>: Si true, indica que el Totem presentara el botón de validacion o botón de salida en pantalla.</p>
<p><strong>menu_activo</strong>: Si true, indica que el usuario recibira un menu en pantalla para seleccionar el sitio de donde viene y el contenido del material que transporta.</p>
<p><strong>barrera_habilitada</strong>: Si true indica que cuándo la pesada haya finalizado la barrera será activada con un pulso de 1 segundo (tiempo = relay2_pulse)</p>
<p><strong>switch_2_Totem_page</strong>: Si true, indica que en caso de quedar la pantalla de configuración en la pantalla el sistema cambiara a la pantalla del Totem en cuanto detecte un nuevo valor de peso. Si esta en false, la pantalla de configuración debe quitarse manualmente desde la pantalla, desde el móvil o desde un PC.</p>
<p><strong>dias_en_BD</strong> = 2. El número indica los dias que pueden mantenerse los archivos relativos a los movimientos en BD una vez que estos hayan sido enviados al servidor.</p>
<p><strong>peso_minimo</strong> = 1000. Valor en Kg leido de la bascula para decir que hay un vehículo entrando.</p>
<p><strong>audio_level</strong> = 20. Nivel de audio inicial de los mensajes.</p>
<p><strong>mante_code_RFID</strong> = 3284727558. Codigo para abrir ventana de mantenimiento.</p>
<p><strong>mante_code_QR</strong> = "this is the key to open the maintenance screen".</p>
<p><strong>init_screen</strong> = true. Habilita inicialización de la pantalladel Nodered un tiempo despues del arranque.</p>
<p><strong>totem_serial</strong> = 20. Serial asignado a este dispositivo.</p>
<p><strong>numero_clicks</strong> = 3. Número de clicks para entrar en ventana de mantenimiento. (viejo)</p>
<p><strong>botonsalida_color_def</strong> = "#026d73". Color del fondo del botón de validación.</p>
<p><strong>botonsalida_txt_color_def</strong> = "orange". Color del texto del botón de validación.</p>
<p>Menu Configuración COMUNICACIONES</p>
<p><strong>modo_totem</strong> = A, B, C, o D. Modo de operación del Totem</p>
<p><strong>A</strong>: QR/RFID; Camara ANPR; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>B</strong>: Camara ANPR; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>C</strong>: QR/RFID; Detector Presencia Vehículo; Botón Validación.</p>
<p><strong>D</strong>: Botón Validación.</p>
<p><strong>ipdevice</strong> = "10.148.171.100".</p>
<p><strong>iprouter</strong> = "10.148.171.1".</p>
<p><strong>dhcp_flag</strong> = true. Modo de trabajo de asignacion de IP. Si = false -> IP Fija</p>
<p><strong>websocket_puerto</strong> = 7000.No utilizado ya que no se puede asignar mediante variable al nodo WS.</p>
<p><strong>websocket_url</strong> = "10.148.171.13". IP del supervisor</p>
<p><strong>movimientosxmqtt</strong> = Si true, indica que el envío de los datos de los movimientos se enviaran vía mqtt. La definición del broker se hace dentro del nodo de mqtt en el editor de nodered.</p>
<p><strong>movimientosxapi</strong> = Si true, indica que el envío de los datos de los movimientos se enviaran vía API. En este caso la url de la api se define en el siguiente parámetro.</p>
<p><strong>url_API=https</strong>: //urlapi.servidor-urbaser.com/api. URL del servidor receptor de datos de movimiento.</p>
<p><strong>path_images</strong> = /home/trypton/node-red/dahua_images/. Carpeta para guardar imagenes matrícula.</p>
<p><strong>path_firmas</strong> = /home/trypton/node-red/firmas/. Carpeta para guardar imagenes de las firmas.</p>
<p><strong>totem_qty</strong> = 2. Nº de Totems en la bascula.</p>
<p><strong>totem_id_remoto</strong> = "totem_4".</p>
<p><strong>totem_id</strong> = "totem_3".  ID asignado a este totem.</p>
<p><strong>numero_vial</strong> = 3. Número del vial asignado al Totem.</p>
<p><strong>ip_totem_pareja</strong> = "10.148.171.126".</p>
<p><strong>supervisor</strong> = false. Indica si el Totem trabaja con un Supervisor o en autonomo.</p>
<p>Menu Configuración MENSAJES</p>
<p><strong>mensaje2</strong> = "Pesada terminada".</p>
<p><strong>mensaje1</strong> = "Peso Estable".</p>
<p><strong>botonsalida_txt_def</strong> = "OK". Texto del botón de Validación</p>
<p><strong>botonsalida_mensaje1_def</strong> = "PESADA FINALIZADA". Mensaje que aparece encima del botón de validación cuándo la pesada ha finalizado..</p>
<p><strong>men_pesaje_listo</strong> = "PESAJE LISTO".</p>
<p><strong>men_peso_estable</strong> = "PESO ESTABLE". Aparece debajo del peso</p>
<p><strong>men_identificacion</strong> = "Acerque QR / tarjeta de ID". Aparece parte inferior de la pantalla</p>
<p><strong>men_salida</strong> = "PUEDE MOVER EL VEHICULO".</p>
<p><strong>id_titulo_matricula</strong> = "MATRICULA".</p>
<p><strong>id_titulo_id</strong> = "TARJETA ID".</p>
<p><strong>id_leido</strong> = "ID leido y enviado". Aparece una vez leido QR / RFID.</p>
<p><strong>id_no_leido</strong> = "ID NO leido". Indica que no se ha recibido QR / RFID en esta pesada.</p>
<p>Menu Configuración CAMARA</p>
<p><strong>camara_matricula</strong> = true. Indica que usamos camara ANPR.</p>
<p><strong>camaraID</strong> = "CAMARA_ENTRADA". Nombre de la camara asociada a este Totem</p>
<p><strong>camara_IP</strong> = "10.148.171.33". IP de la camara asociada a este Totem.</p>
<p>Menu Configuración SENSORES</p>
<p><strong>tipo_de_lector</strong> = "ER-80". Puede ser Kimaldi u otro.</p>
<p><strong>rfid_invertido</strong> = false. Indica si es necesario darle la vuelta al código hexadecimal leido del RFID.</p>
<p><strong>sensor_distancia_local</strong> = true. Indica que disponemos de sensor de distancia local.</p>
<p><strong>sensor_distancia_remoto</strong> = true. Indica que disponemos de sensor de distancia remoto, es decir que el Totem pareja (en la misma bascula) envía valor del sensor de distancia.</p>
<p><strong>distance_min</strong> = 2000. Distancia a partir de la cual indica que no hay ningún objeto</p>
<p><strong>min_cnt_in_ok</strong> = 1. Nº de veces seguidas que el sensor encuentra distancia < distance_minpara indicar que hay presencia de un objeto.</p>
<p><strong>min_cnt_out_ok</strong> = 4. Nº de veces seguidas que el sensor encuentra distancia > distance_minpara indicar que hay ausencia de objeto.</p>
<p>Menu Configuración TIEMPOS</p>
<p><strong>timer1</strong> = 1000.Este parametro actualmente no se utiliza.</p>
<p><strong>tiempo_mensaje</strong> = 10000.Este parametro actualmente no se utiliza.</p>
<p><strong>relay2_pulse</strong> = 1000. Nº milisegundos del pulso de apertura de la barrera.</p>
<p><strong>config_window_time</strong> = 500. Nº de milisegundos para leer los clicks.</p>
<p><strong>time_max_2_bascula</strong> = 10000. Tiempo desde recepcion del ANPR0 (antes que el vehículo entre en bascula) hasta la activacion de la bascula.</p>
<p><strong>wait_time_to_send_plate</strong> = 3000. Tiempo de espera para enviar la matricula, después de haber recibido ANPR0valido, por si llega un nuevo snapshot antes de 3s</p>
<p><strong>max_wait_time_to_send_plate</strong> = 8000. Tiempo de espera para enviar la matricula, en caso deNOtener ANPR0validopor si llega un nuevo ANPR.</p>
<p><strong>max_time_bascula_sensor</strong> = 4000. Tiempo maximo permitido entre activacion de bascula y activacion del detector de vehículos.Actualmente no se utiliza.</p>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+626
View File
@@ -0,0 +1,626 @@
/* =============================== BASE GLOBAL =============================== */
body,
html {
margin: 0;
padding: 0;
font-family: "Segoe UI", sans-serif;
background-color: #fff;
color: #333;
}
.container {
max-width: 400px;
margin: auto;
padding: 20px;
}
#pantalla-tabla-general .container {
padding-left: 8px;
padding-right: 8px;
}
hr {
margin: 10px 0 20px;
border: 1px solid #ccc;
}
/* =============================== ENCABEZADO =============================== */
.header,
.encabezado {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.logo {
width: 40px;
height: 40px;
}
.titulo,
h3 {
text-align: center;
margin-top: 10px;
font-size: 26px;
font-weight: bold;
}
/* =============================== PANTALLAS SPA =============================== */
.pantalla {
display: none;
}
.pantalla.visible {
display: block;
}
/* =============================== BOTONES =============================== */
.btn {
padding: 16px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.btn.big {
width: 100%;
margin-top: 10px;
}
/* Colores */
.btn.yellow,
.btn.amarillo {
background: #f1c40f;
color: #444;
}
.btn.orange,
.btn.naranja {
background: #e67e22;
}
.btn.green,
.btn.verde {
background: #28a745;
}
.btn.teal,
.btn.verde-oscuro {
background: #049a81;
}
.btn.red,
.btn.rojo {
background: firebrick;
}
.btn.blue,
.btn.azul {
background: #3498db;
}
.btn.darkblue,
.btn.azuloscuro {
background: #0567a9;
}
.btn.aqua,
.btn.celeste {
background: #04fbc7;
color: #333;
}
.btn.purple,
.btn.morado {
background: #9b59b6;
}
.btn.gray,
.btn.gris {
background: #707b7c;
}
.btn.gold,
.btn.mostaza {
background: #DAB957;
color: #444;
}
/* =============================== BOTONERAS / GRID =============================== */
.grid-vertical {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 20px 0;
}
.botonera-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-width: 400px;
margin: 20px auto;
}
.botonera-vertical {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
margin: auto;
}
/* =============================== COMPONENTES VISUALES =============================== */
.circle {
width: 100px;
height: 100px;
margin: 20px auto;
border-radius: 50%;
background: #239b56;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.imagen {
display: flex;
justify-content: center;
margin: 12px 0;
}
.imagen img {
max-width: 85vw;
max-height: 43vh;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.matricula-nav {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 12px;
}
.placa {
background: white;
padding: 8px 16px;
border-radius: 10px;
font-size: 24px;
font-weight: bold;
color: #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 100px;
text-align: center;
}
.fecha {
text-align: center;
font-size: 14px;
font-weight: bold;
color: #555;
margin-top: 8px;
}
.camara-row {
display: flex;
justify-content: space-between;
font-weight: bold;
margin: 10px 0;
}
.desarrollo {
text-align: center;
margin: 20px 0;
}
/* =============================== TABLA EDITABLE =============================== */
table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 8px;
overflow: hidden;
table-layout: fixed;
word-wrap: break-word;
}
th,
td {
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
text-align: left;
}
th {
background-color: #007acc;
color: white;
font-weight: bold;
width: 40%;
}
td {
background-color: #fafafa;
}
td[contenteditable="true"] {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 25px;
}
.btn-actualizar,
.btn-cancelar {
padding: 12px 24px;
font-weight: bold;
font-size: 16px;
border-radius: 8px;
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn-actualizar {
background-color: #28a745;
color: white;
}
.btn-actualizar:hover {
background-color: #218838;
}
.btn-cancelar {
background-color: #dc3545;
color: white;
}
.btn-cancelar:hover {
background-color: #c82333;
}
/*----------------------------------------------- MODAL -------------------------------------*/
.modal {
display: none;
position: fixed;
z-index: 999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
background-color: #fff;
margin: 15% auto;
padding: 20px;
border-radius: 10px;
max-width: 300px;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.modal-buttons {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
/*----------------------------------------------- PANTALLA PRINCIPAL DEL TOTEM -------------------------------------*/
.peso-section {
display: flex;
justify-content: center;
align-items: baseline;
font-weight: bold;
margin: 5px 0 5px;
font-size: 18px;
}
.peso-display-linea {
display: flex;
justify-content: space-between; /* separa izquierda y derecha */
align-items: baseline;
margin: 20px 0;
}
.peso-display {
display: flex;
align-items: baseline;
gap: 8px;
font-size: 44px;
font-weight: bold;
}
.peso-display.derecha {
justify-content: flex-end;
}
.peso-label {
margin-right: 10px;
font-size: 26px;
}
.peso-valor {
font-size: 90px;
color: #2c3e50;
font-weight: bold;
}
.peso-unidad {
margin-left: 8px;
font-size: 18px;
}
.status-line {
display: flex;
justify-content: space-between;
font-size: 16px;
margin: 8px 0;
}
.status-line .label {
font-weight: bold;
}
.status-line.small {
font-size: 14px;
color: #555;
}
.led-semaforo {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #ccc;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
margin-left: 10px;
display: inline-block;
}
.semaforo-display {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
.led-semaforo.led {
box-shadow: 0 0 12px currentColor, 0 0 3px currentColor inset;
}
.etiqueta {
min-width: 90px;
}
.labelMat{
font-size: 24px;
color: red;
font-weight: bold;
}
.labelPE {
font-size: 18px;
color: blue;
font-weight: bold;
}
.labelValue {
font-size: 18px;
color: blue;
}
#pantalla-barrera .container {
text-align: center;
}
/*-------------------------------------------------- PANTALLA DE LA BARRERA ---------------------------------------------------------------------*/
.barrera-icono {
margin: 20px auto;
height: 100px;
}
.barrera-icono img {
max-height: 100px;
}
/*-------------------------------------------------- TIPOS DE CONFIGURACION ---------------------------------------------------------------------*/
#tipo-configuracion .parametro-row {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
#tipo-configuracion input {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.parametro-row button {
font-size: 20px;
line-height: 20px;
padding: 0;
text-align: center;
}
.botonera-tipo {
margin-top: 15px;
display: flex;
justify-content: center;
gap: 10px;
}
/* Estilo base para la fila */
.parametro-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
width: 100%;
}
.parametro-row input.parametro {
flex: 1 1 50%;
min-width: 100px;
max-width: 300px;
padding: 4px;
font-size: 14px;
}
.parametro-row input.valor {
flex: 0 0 30%;
min-width: 60px;
max-width: 150px;
padding: 4px;
font-size: 14px;
text-align: center;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
transform: scale(1.8);
margin: 0;
}
.parametro-row button {
flex: 0 0 30px;
width: 30px;
height: 30px;
font-size: 14px;
padding: 0;
margin: 0;
}
@media (max-width: 600px) {
.parametro-row input.parametro {
flex: 1 1 40%;
font-size: 12px;
}
.parametro-row input.valor {
flex: 1 1 30%;
font-size: 12px;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
/*zoom: 1.5; */
transform: scale(1.5) ;
margin: 4px;
}
.parametro-row button {
flex: 0 0 25px;
width: 25px;
height: 25px;
font-size: 12px;
}
/*-------------------------------------------------- PANTALLA DE LA ANALISIS ---------------------------------------------------------------------*/
.panel-trama {
margin-top: 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px;
background: #fafafa;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
}
.panel-trama h4 {
margin: 0 0 10px 0;
font-size: 1rem;
letter-spacing: .02em;
}
.trama-grid {
display: grid;
grid-template-columns: 120px 1fr;
gap: 8px 14px;
align-items: start;
}
.trama-label {
font-weight: 600;
color: #374151;
text-transform: uppercase;
font-size: .78rem;
letter-spacing: .04em;
}
.trama-valor {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 6px 8px;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
}
.trama-valor.small {
display: inline-block;
padding: 4px 8px;
}
.input-trama {
display: inline-block;
width: 150px; /* ancho deseado */
padding: 16px; /* igual que .btn */
font-size: 16px; /* igual que .btn */
font-weight: bold; /* igual que .btn */
line-height: 16px; /* clave para que coincida */
color: #333;
background-color: #f2f2f2; /* gris clarito */
border: none; /* igual que .btn */
border-radius: 0; /* sin bordes redondeados */
box-shadow: none; /* sin sombra como el botón */
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
}
/*-------------------------------------------------- IMAGEN INICIAL DEL SNAPSHOT ---------------------------------------------------------------------*/
/*
#foto {
width: 65vw;
height: 30vh;
background-color: #eee;
border-radius: 10px;
object-fit: contain;
display: block;
margin: auto;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
*/
@import url("../uibuilder/uib-brand.min.css");
.comboContainer {
display: none;
transition: opacity 0.3s ease;
opacity: 0;
}
.comboContainer.visibleCombo {
display: block;
opacity: 1;
}
+162
View File
@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interfaz Totem</title>
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="../uibuilder/uibuilder.iife.min.js"></script>
<link rel="stylesheet" href="./index.css">
<script src="./index.js" defer></script>
</head>
<body>
<!-- -------------------------------------- PANTALLA INICIAL DEL TOTEM --------------------------------------------------- -->
<div id="pantalla-principal" class="pantalla visible">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h1 class="titulo">Raspipeso</h1>
</div>
<hr />
<div class="peso-display-linea">
<div class="peso-display izquierda">
<span id="peso1" class="peso-valor1">--</span>
</div>
<div class="peso-display izquierda">
<span id="peso2" class="peso-valor2">--</span>
</div>
</div>
<div class="peso-display-linea">
<div class="peso-display izquierda">
<span id="peso3" class="peso-valor3">--</span>
</div>
<div class="peso-display izquierda">
<span id="peso4" class="peso-valor4">--</span>
</div>
</div>
<div class="botonera-grid">
<button class="btn verde big" data-action="menu-principal1">CONFIG BASCULA1</button>
<button class="btn verde big" data-action="menu-principal2">CONFIG BASCULA2</button>
</div>
<div class="botonera-grid">
<button class="btn verde big" data-action="menu-principal3">CONFIG BASCULA3</button>
<button class="btn verde big" data-action="menu-principal4">CONFIG BASCULA4</button>
</div>
</div>
</div>
<!-- -------------------------------------- MENU PRINCIPAL DE CONFIGURACION --------------------------------------------------- -->
<div id="pantalla-menu-principal" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">CONFIGURACIÓN RASPIPESO</h3>
<hr>
<div class="grid-vertical">
<button class="btn blue" data-action="editar">EDITAR CONFIGURACION</button>
<button class="btn green" data-action="aplicar">APLICAR CONFIGURACION</button>
<button class="btn purple" data-action="otras">OTRAS ACCIONES</button>
<button class="btn teal" data-action="salir">SALIR</button>
</div>
</div>
</div>
<!-- --------------------------------------MENU EDITAR CONFIGURACION--------------------------------------------------- -->
<div id="pantalla-editar" class="pantalla"> <div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">EDITAR CONFIGURACION</h3>
<hr />
<div class="botonera-vertical">
<button data-action="parametros" class="btn azuloscuro">PARAMETROS</button>
<button data-action="rs232" class="btn mostaza">RS232</button>
<button data-action="general" class="btn purple">GENERAL</button>
<button data-action="salvar" class="btn verde">SALVAR MODIFICACIONES</button>
<button data-action="cancelar" class="btn verde-oscuro">SALIR</button>
</div> </div>
</div>
<!-- -------------------------------------- MENU OTRAS ACCIONES --------------------------------------------------- -->
<div id="pantalla-otras" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">OTRAS ACCIONES</h3>
<hr>
<div class="desarrollo">
<h3 style="color:#2e86c1;">EN DESARROLLO</h3>
</div>
<div class="grid-vertical">
<button class="btn aqua" data-action="reboot">REBOOT RPi</button>
<button class="btn red" data-action="apagar">APAGAR RPi</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- ------------------------------- PANTALLA DE TABLA DINAMICA PARA MOSTRAR PARAMETROS DE CONFIGURACION------------------------------------------------->
<div id="pantalla-tabla-general" class="pantalla">
<div class="container">
<h2 style="text-align:center;">Parámetros del Sistema</h2>
<div class="button-container">
<table id="table2">
<thead>
<tr>
<th style="text-align:center;">Parámetro</th>
<th style="text-align:center;">Valor</th>
</tr>
</thead>
<tbody id="config-table-body">
<!-- Se rellena dinámicamente -->
</tbody>
</table>
</div>
<div class="button-container">
<button class="custom-btn btn-actualizar" onclick="sendRow()">ACTUALIZAR</button>
<button class="custom-btn btn-cancelar" onclick="sendCancel()">CANCELAR</button>
</div>
</div>
</div>
<!-- --------------------------------------MODAL--------------------------------------------------- -->
<div id="confirm-modal" class="modal">
<div class="modal-content">
<p id="modal-text">¿Deseas guardar los cambios?</p>
<div class="modal-buttons">
<button id="btn-modal-si" class="btn verde" onclick="confirmGuardar()"></button>
<button id="btn-modal-no" class="btn rojo" onclick="cancelarGuardar()">No</button>
</div>
</div>
</div>
</body>
</html>
+185
View File
@@ -0,0 +1,185 @@
/* global uibuilder */
uibuilder.start();
// Navegación entre pantallas
function mostrarPantalla(id) {
document.querySelectorAll('.pantalla').forEach(div =>
div.classList.toggle('visible', div.id === id)
);
}
let currentTopic = "";
let result = [];
//****************************************************RECEPCION DE MENSAJES DESDE NODE_RED ***************************************************************** */
uibuilder.onChange('msg', msg => {
if (msg.payload && msg.payload.pantalla) {
mostrarPantalla('pantalla-' + msg.payload.pantalla);
}
// tabla dinamica
if (msg.topic == "general" || msg.topic == "rs232" || msg.topic == "parametros"){
if (!msg.tabla || !Array.isArray(msg.tabla)) return;
const tbody = document.getElementById('config-table-body');
tbody.innerHTML = '';
currentTopic = msg.topic || 'general';
msg.tabla.forEach(item => { //generamos dinamixamente la tabla que vera el usuario en el dispositivo (PC o smartphone).
// notese que la celda 2 de cada fila es del tipo editable.
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = item.parametro;
const cell2 = document.createElement('td');
cell2.contentEditable = true;
cell2.textContent = item.valor;
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row); // enviamos la tabla al elemento html (config-table-body)
});
}
// configuracion del pop up de notificaciones
if (msg.modalText) {
document.getElementById('modal-text').textContent = msg.modalText;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
if (msg.modalType === "notificacion") {
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
} else {
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = msg.modalCallback || null;
}
document.getElementById('confirm-modal').style.display = 'block';
}
// ======================================== Datos para la Pantalla Principal ========================================================================
if (msg.hasOwnProperty("peso1")) document.getElementById('peso1').textContent = msg.peso1 || "---";
if (msg.hasOwnProperty("peso2")) document.getElementById('peso2').textContent = msg.peso2 || "---";
if (msg.hasOwnProperty("peso3")) document.getElementById('peso3').textContent = msg.peso3 || "---";
if (msg.hasOwnProperty("peso4")) document.getElementById('peso4').textContent = msg.peso4 || "---";
});
//****************************************************************************************************************************************************** */
// Detectar clics en botones
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action]')) {
const action = ev.target.getAttribute('data-action');
console.log('⏺ Acción botón:', action);
let payload = { seccion: action };
uibuilder.send({ payload });
}
});
//******************************************* FUNCIONES ******************************************************** */
// Acciones a partir de botones en html
function sendRow() {// Aqui llega cuando se hace click en el boton Actualizar de las tablas dinamicas
// recoge el contenido de la tabla en ese instante y saca Notificacion de guardar con opcion Si o No
const rows = document.querySelectorAll('#config-table-body tr');
result = []; // inicializamos result para poner exactamente la tabla actual
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// normaliza a booleano si corresponde
if (valor === "true") valor = true;
if (valor === "false") valor = false;
result.push({ parametro, valor });
});
// Mostramos directamente la confirmación y definimos la callback
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({
payload: { table: result, seccion: "respuesta" },
topic: currentTopic,
origen: "actualizar"
});
});
}
function sendCancel() { // boton Cancelar de las tablas dinamicas
uibuilder.send({ payload: {seccion:'cancel-table'}, topic: currentTopic });
}
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
document.getElementById('confirm-modal').style.display = 'none';
if (typeof window.modalCallback === 'function') {
const cb = window.modalCallback;
window.modalCallback = null; // limpia para la siguiente vez
cb(); // ejecuta
} else if (typeof window.modalCallback === 'string') {
uibuilder.send({ payload: { table: result }, modalCallback: window.modalCallback, origen: "modal" });
}
}
/*
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
uibuilder.send({ payload: { table: result }, topic: currentTopic, origen: "modal-tabla" });
document.getElementById('confirm-modal').style.display = 'none';
}
*/
function cancelarGuardar() { // Respuesta Negativa del Modal de tabla dinamica
result = [];
document.getElementById('confirm-modal').style.display = 'none';
}
//************************* FUNCIONES DE TIPOS DE CONFIGURACION *****************************/
//==========================================FUNCIONES PARA MANEJAR LAS NOTIFICACIONES ======================================================
function mostrarConfirmacion(texto, callback) {
document.getElementById('modal-text').textContent = texto;
// Restablece estado normal del modal
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = callback;
document.getElementById('confirm-modal').style.display = 'block';
}
function mostrarNotificacion(texto) {
document.getElementById('modal-text').textContent = texto;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
document.getElementById('confirm-modal').style.display = 'block';
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"baseUrl": "./src",
"typeRoots": ["./types"]
},
"include": ["types", "src/**/*.js"]
}
+12
View File
@@ -0,0 +1,12 @@
/// <reference path="./uibuilder.module.d.ts" />
/**
* Make the uibuilder instance globally available (from uibuilder.module.d.ts)
* @version 7.3.0
*/
declare global {
// Use typeof import to reference the Uib class from the module
const uibuilder: import("./uibuilder.module").Uib;
}
export {};
+647
View File
@@ -0,0 +1,647 @@
/**
* Type definitions for uibuilder.module.js
* WCAG 2.2 AA, ESLint v9, Shift-Left security, and project conventions applied.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export type HtmlString = string
/** Column metadata for tables */
export interface ColumnDefinition {
index: number,
hasName: boolean,
title: string,
name?: string,
key?: string | number,
dataType?: 'string' | 'date' | 'number' | 'html',
editable?: boolean,
}
/** Options for building HTML tables */
export interface TableOptions {
cols?: ColumnDefinition[],
parent?: HTMLElement | string,
allowHTML?: boolean,
}
/** Options for tblAddListener */
export interface TableListenerOptions {
eventScope?: 'row' | 'cell',
returnType?: 'text' | 'html',
pad?: number,
send?: boolean,
logLevel?: string | number,
eventType?: string,
}
/** Options for notification */
export interface NotificationConfig {
title?: string,
body?: string,
return?: boolean,
[key: string]: any,
}
/**
* Uibuilder main class
* @typicalname uibuilder
* @description The client-side Front-End JavaScript for uibuilder in HTML Module form.
* Provides a number of global objects that can be used in your own JavaScript.
* See the docs folder `./docs/uibuilder.module.md` for details of how to use this fully.
* @version 7.3.0
* @author Julian Knight (Totally Information)
*/
export class Uib {
/**
* Static metadata for the Uibuilder client
*/
static _meta: {
version: string,
type: string,
displayName: string,
}
/** Client ID set by uibuilder on connect */
clientId: string
/** The collection of cookies provided by uibuilder */
cookies: Record<string, string>
/** Copy of last control msg object received from server */
ctrlMsg: object
/** Is Socket.IO client connected to the server? */
ioConnected: boolean
/** Is the library running from a minified version? */
isMinified: boolean
/** Is the browser tab containing this page visible or not? */
isVisible: boolean
/** Remember the last page (re)load/navigation type: navigate, reload, back_forward, prerender */
lastNavType: string
/** Max msg size that can be sent over Socket.IO - updated by "client connect" msg receipt */
maxHttpBufferSize: number
/** Last std msg received from Node-RED */
msg: object
/** Number of messages sent to server since page load */
msgsSent: number
/** Number of messages received from server since page load */
msgsReceived: number
/** Number of control messages sent to server since page load */
msgsSentCtrl: number
/** Number of control messages received from server since page load */
msgsCtrlReceived: number
/** Is the client online or offline? */
online: boolean
/** Last control msg object sent via uibuilder.send() */
sentCtrlMsg: object
/** Last std msg object sent via uibuilder.send() */
sentMsg: object
/** Placeholder to track time offset from server, see fn socket.on(ioChannels.server ...) */
serverTimeOffset: number | null
/** Placeholder for a socket error message */
socketError: string | null
/** Tab identifier from session storage */
tabId: string
/** Actual name of current page (set in constructor) */
pageName: string | null
/** Is the DOMPurify library loaded? Updated in start() */
purify: boolean
/** Is the Markdown-IT library loaded? Updated in start() */
markdown: boolean
/** Current URL hash. Initial set is done from start->watchHashChanges via a set to make it watched */
urlHash: string
/** Default originator node id - empty string by default */
originator: string
/** Optional default topic to be included in outgoing standard messages */
topic?: string
/** Either undefined or a reference to a uib router instance. Set by uibrouter, do not set manually. */
uibrouterinstance?: any
/** Set by uibrouter, do not set manually */
uibrouter_CurrentRoute?: any
/** Internal: auto-send ready flag */
autoSendReady: boolean
/** Node-RED setting (via cookie) */
httpNodeRoot: string
/** Socket.IO namespace - unique to each uibuilder node instance */
ioNamespace: string
/** Socket.IO path */
ioPath: string
/** Starting delay factor for subsequent reconnect attempts */
retryFactor: number
/** Starting retry ms period for manual socket reconnections workaround */
retryMs: number
/** Prefix for all uib-related localStorage */
storePrefix: string
/** Whether uibuilder client has started */
started: boolean
/** Socket.IO connection options */
socketOptions: object
// --- Getters/Setters ---
logLevel: number
meta: typeof Uib._meta
/**
* Set uibuilder properties to a new value - works on any property except _* or #*
* Also triggers any event listeners.
* @param prop Any uibuilder property who's name does not start with a _ or #
* @param val The set value of the property or a string declaring that a protected property cannot be changed
* @param store If true, the variable is also saved to the browser localStorage if possible
* @param autoload If true & store is true, on load, uib will try to restore the value from the store automatically
* @returns Input value
*/
set(prop: string, val: any, store?: boolean, autoload?: boolean): any
/**
* Get the value of a uibuilder property
* @param prop The name of the property to get as long as it does not start with a _ or #
* @returns The current value of the property
*/
get(prop: string): any
/**
* Write to localStorage if possible. Console error output if can't write
* Also uses this.storePrefix
* @param id localStorage var name to be used (prefixed with 'uib_')
* @param value value to write to localstore
* @param autoload If true, on load, uib will try to restore the value from the store
* @returns True if succeeded else false
*/
setStore(id: string, value: any, autoload?: boolean): boolean
/**
* Attempt to get and re-hydrate a key value from localStorage
* @param id The key of the value to attempt to retrieve
* @returns The re-hydrated value of the key or null if key not found, undefined on error
*/
getStore(id: string): any
/**
* Remove a given id from the uib keys in localStorage
* @param id The key to remove
*/
removeStore(id: string): void
/**
* Returns a list of uibuilder properties (variables) that can be watched with onChange
* @returns List of uibuilder managed variables
*/
getManagedVarList(): Record<string, string>
/**
* Returns a list of currently watched variables
* @returns List of watched variable names
*/
getWatchedVars(): string[]
/**
* Register on-change event listeners for uibuilder tracked properties
* @param prop The property of uibuilder that we want to monitor
* @param callback The function that will run when the property changes, parameter is the new value of the property after change
* @returns A reference to the callback to cancel
*/
onChange(prop: string, callback: (val: any) => void): number
/**
* Cancel a previously registered onChange event listener
* @param prop The property name
* @param cbRef The callback reference number
*/
cancelChange(prop: string, cbRef: number): void
/**
* Register a change callback for a specific msg.topic
* @param topic The msg.topic we want to listen for
* @param callback The function that will run when an appropriate msg is received
* @returns A reference to the callback to cancel
*/
onTopic(topic: string, callback: (msg: any) => void): number
/**
* Cancel a previously registered onTopic event listener
* @param topic The topic name
* @param cbRef The callback reference number
*/
cancelTopic(topic: string, cbRef: number): void
/**
* Returns a new array containing the intersection of the 2 input arrays
* @param a1 Array to check
* @param a2 Array to intersect
* @returns The intersection of the 2 arrays (may be an empty array)
*/
arrayIntersect<T>(a1: T[], a2: T[]): T[]
/**
* Copies a uibuilder variable to the browser clipboard
* @param varToCopy The name of the uibuilder variable to copy to the clipboard
*/
copyToClipboard(varToCopy: string): void
/**
* Does the chosen CSS Selector currently exist?
* @param cssSelector Required. CSS Selector to examine for visibility
* @param msg Optional, default=true. If true also sends a message back to Node-RED
* @returns True if the element exists
*/
elementExists(cssSelector: string, msg?: boolean): boolean
/**
* Format a number using the INTL standard library
* @param value Number to format
* @param decimalPlaces Number of decimal places to include
* @param intl standard locale spec, e.g. "ja-JP" or "en-GB"
* @param opts INTL library options object
* @returns formatted number
*/
formatNumber(value: number, decimalPlaces?: number, intl?: string, opts?: object): string
/**
* Attempt to get rough size of an object
* @param obj Any serialisable object
* @returns Rough size of object in bytes or undefined
*/
getObjectSize(obj: any): number | undefined
/**
* Returns true if a uibrouter instance is loaded, otherwise returns false
* @returns true if uibrouter instance loaded else false
*/
hasUibRouter(): boolean
/**
* Only keep the URL Hash & ignoring query params
* @param url URL to extract the hash from
* @returns Just the route id
*/
keepHashFromUrl(url: string): string
/**
* Custom logging function
* @param args Arguments to log
*/
log(...args: any[]): void
/**
* Makes a null or non-object into an object. If thing is already an object.
* If not null, moves "thing" to {payload:thing}
* @param thing Thing to check
* @param property property that "thing" is moved to if not null and not an object. Default='payload'
* @returns Object
*/
makeMeAnObject(thing: any, property?: string): object
/**
* Navigate to a new page or a new route (hash)
* @param url URL to navigate to. Can be absolute or relative (to current page) or just a hash for a route change
* @returns The new window.location string
*/
navigate(url: string): Location
/**
* Convert a string attribute into a variable/constant reference
* Used to resolve data sources in attributes
* @param path The string path to resolve, must be relative to the `window` global scope
* @returns The resolved data source or null
*/
resolveDataSource(path: string): any
/**
* Fast but accurate number rounding
* @param num The number to be rounded
* @param decimalPlaces Number of DP's to round to
* @returns Rounded number
*/
round(num: number, decimalPlaces: number): number
/**
* Set the default originator. Set to '' to ignore. Used with uib-sender.
* @param originator A Node-RED node ID to return the message to
*/
setOriginator(originator?: string): void
/**
* HTTP Ping/Keep-alive - makes a call back to uibuilder's ExpressJS server and receives a 204 response
* Can be used to keep sessions alive.
* @param ms Repeat interval in ms
*/
setPing(ms?: number): void
/**
* Convert JSON to Syntax Highlighted HTML
* @param json A JSON/JavaScript Object
* @returns Object reformatted as highlighted HTML
*/
syntaxHighlight(json: object): HtmlString
/**
* Returns true/false or a default value for truthy/falsy and other values
* @param val The value to test
* @param deflt Default value to use if the value is not truthy/falsy
* @returns The truth! Or the default
*/
truthy(val: any, deflt: any): boolean | any
/**
* Joins all arguments as a URL string
* @param paths URL fragments
* @returns Joined URL string
*/
urlJoin(...paths: string[]): string
/**
* Turn on/off/toggle sending URL hash changes back to Node-RED
* @param toggle Optional on/off/etc
* @returns True if we will send a msg to Node-RED on a hash change
*/
watchUrlHash(toggle?: any): boolean
/**
* DEPRECATED FOR NOW - wasn't working properly.
* Is the chosen CSS Selector currently visible to the user? NB: Only finds the FIRST element of the selection.
* @returns False
*/
elementIsVisible(): false
// --- UI handlers ---
/**
* Simplistic jQuery-like document CSS query selector, returns an HTML Element.
* If the selected element is a <template>, returns the first child element.
* @param cssSelector A CSS Selector that identifies the element to return
* @returns Selected HTML element or null
*/
$: (cssSelector: string) => HTMLElement | null
/**
* CSS query selector that returns ALL found selections as an array of elements.
* @param cssSelector A CSS Selector that identifies the elements to return
* @returns Array of DOM elements/nodes. Array is empty if selector is not found.
*/
$$: (cssSelector: string) => HTMLElement[]
/**
* Reference to the full ui library
*/
$ui: any
/**
* Add one or several class names to an element
* @param classNames Single or array of classnames
* @param el HTML Element to add class(es) to
*/
addClass(classNames: string | string[], el: HTMLElement): void
/**
* Apply a source template tag to a target html element
* @param source The source element
* @param target The target element
* @param onceOnly If true, the source will be adopted (the source is moved)
*/
applyTemplate(source: HTMLElement, target: HTMLElement, onceOnly: boolean): void
/**
* Builds an HTML table from an array (or object) of objects
* @param data Input data array or object
* @param opts Table options
* @returns Output HTML Element
*/
buildHtmlTable(data: object[] | object, opts?: TableOptions): HTMLTableElement | HTMLParagraphElement
/**
* Directly add a table to a parent element.
* @param data Input data array or object
* @param opts Build options
*/
createTable(data?: object[] | any[], opts?: TableOptions): void
/**
* Converts markdown text input to HTML if the Markdown-IT library is loaded
* Otherwise simply returns the text
* @param mdText The input markdown string
* @returns HTML (if Markdown-IT library loaded and parse successful) or original text
*/
convertMarkdown(mdText: string): string
/**
* ASYNC: Include HTML fragment, img, video, text, json, form data, pdf or anything else from an external file or API
* @param url The URL of the source file to include
* @param uiOptions Object containing properties recognised by the _uiReplace function. Must at least contain an id
*/
include(url: string, uiOptions: object): Promise<void>
/**
* Attach a new remote script to the end of HEAD synchronously
* @param url The url to be used in the script src attribute
*/
loadScriptSrc(url: string): void
/**
* Attach a new remote stylesheet link to the end of HEAD synchronously
* @param url The url to be used in the style link href attribute
*/
loadStyleSrc(url: string): void
/**
* Attach a new text script to the end of HEAD synchronously
* @param textFn The text to be loaded as a script
*/
loadScriptTxt(textFn: string): void
/**
* Attach a new text stylesheet to the end of HEAD synchronously
* @param textFn The text to be loaded as a stylesheet
*/
loadStyleTxt(textFn: string): void
/**
* Load a dynamic UI from a JSON web response
* @param url URL that will return the ui JSON
*/
loadui(url: string): void
/**
* Remove All, 1 or more class names from an element
* @param classNames Single or array of classnames. If undefined, "" or null, remove all classes
* @param el HTML Element to remove class(es) from
*/
removeClass(classNames: string | string[] | undefined | null, el: HTMLElement): void
/**
* Replace or add an HTML element's slot from text or an HTML string
* WARNING: Executes <script> tags! And will process <style> tags.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param slot The slot content we are trying to add/replace (defaults to empty string)
*/
replaceSlot(el: Element, slot: any): void
/**
* Replace or add an HTML element's slot from a Markdown string
* Only does something if the markdownit library has been loaded to window.
* Will use DOMPurify if that library has been loaded to window.
* @param el Reference to the element that we want to update
* @param component The component we are trying to add/replace
*/
replaceSlotMarkdown(el: Element, component: any): void
/**
* Sanitise HTML to make it safe - if the DOMPurify library is loaded
* Otherwise just returns that HTML as-is.
* @param html The input HTML string
* @returns The sanitised HTML or the original if DOMPurify not loaded
*/
sanitiseHTML(html: string): string
/**
* Add table event listener that returns the text or html content of either the full row or a single cell
* @param tblSelector The table CSS Selector
* @param options Additional options
* @param out A variable reference that will be updated with the output data upon a click event
*/
tblAddListener(tblSelector: string, options?: TableListenerOptions, out?: object): void
/**
* Add a row to a table element in the DOM.
* @param tbl The table element or selector to add the row to
* @param rowData The data for the new row (object or array)
* @param options Optional configuration for row creation
* @returns The created HTMLTableRowElement
*/
tblAddRow(tbl: string | HTMLTableElement, rowData: object | any[], options?: object): HTMLTableRowElement
/**
* Remove a row from a table element in the DOM.
* @param tbl The table element or selector to remove the row from
* @param rowIndex The index of the row to remove
* @param options Optional configuration for row removal
*/
tblRemoveRow(tbl: string | HTMLTableElement, rowIndex: number, options?: object): void
/**
* Show a dialog (notification or alert) in the UI.
* @param type The dialog type: 'notify' or 'alert'
* @param ui The UI configuration object for the dialog
* @param msg Optional message object to include
*/
showDialog(type: 'notify' | 'alert', ui: object, msg?: object): void
/**
* Apply a UI definition (JSON) to the current page.
* @param json The UI definition object
*/
ui(json: object): void
/**
* Get properties or values from UI elements matching a selector.
* @param cssSelector The CSS selector for the elements
* @param propName Optional property name to retrieve
* @returns Array of property values or elements
*/
uiGet(cssSelector: string, propName?: string): any[]
/**
* Enhance a DOM element with a UI component definition.
* @param el The element to enhance
* @param component The component definition or configuration
*/
uiEnhanceElement(el: any, component: any): void
// --- DOM/HTML cache ---
/**
* Clear the cached HTML content from memory or storage.
*/
clearHtmlCache(): void
/**
* Restore HTML content from the cache into the DOM.
*/
restoreHtmlFromCache(): void
/**
* Save the current HTML content to the cache for later restoration.
*/
saveHtmlCache(): void
// --- Message Handling ---
/**
* Send a standard message to Node-RED via Socket.IO.
* @param msg The message object to send
* @param originator Optional Node-RED node ID to return the message to
*/
send(msg: object, originator?: string): void
/**
* Send a message to a specific room via Socket.IO.
* @param room The room name
* @param msg The message to send
*/
sendRoom(room: string, msg: any): void
/**
* Join a Socket.IO room.
* @param room The room name to join
*/
joinRoom(room: string): void
/**
* Leave a Socket.IO room.
* @param room The room name to leave
*/
leaveRoom(room: string): void
/**
* Send a control message to Node-RED via Socket.IO.
* @param msg The control message object to send
*/
sendCtrl(msg: object): void
/**
* Send a custom message on a specific channel via Socket.IO.
* @param channel The custom channel name
* @param msg The message object to send
*/
sendCustom(channel: string, msg: object): void
/**
* Upload a file to the server via Socket.IO.
* @param file The file to upload
* @param meta Optional metadata to send with the file
*/
uploadFile(file: File, meta?: object): void
// --- Socket.IO ---
/**
* Connect the Socket.IO client to the server.
*/
connect(): void
/**
* Disconnect the Socket.IO client from the server.
*/
disconnect(): void
// --- Startup ---
/**
* Start the uibuilder client, initializing all features and connections.
* @param options Optional startup options
*/
start(options?: object): void
// --- Show/hide ---
/**
* Show or hide the message area in the UI.
* @param showHide If true, show the message area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the message area is shown, false if hidden
*/
showMsg(showHide?: boolean, parent?: string): boolean
/**
* Show or hide the status area in the UI.
* @param showHide If true, show the status area; if false, hide it
* @param parent Optional parent selector or element
* @returns True if the status area is shown, false if hidden
*/
showStatus(showHide?: boolean, parent?: string): boolean
// --- Watchers ---
/**
* Watch a DOM element for changes and optionally send updates to Node-RED.
* @param cssSelector The CSS selector to watch
* @param startStop Start, stop, or toggle the watcher
* @param send If true, send updates to Node-RED
* @param showLog If true, log watcher activity
* @returns True if watching, false otherwise
*/
uiWatch(cssSelector: string, startStop?: boolean | 'toggle', send?: boolean, showLog?: boolean): boolean
/**
* Watch the DOM for changes (e.g., for dynamic UI updates).
* @param startStop Start or stop watching
*/
watchDom(startStop: boolean): void
// --- Notifications ---
/**
* Show a notification or alert in the UI.
* @param config Notification configuration or string message
* @returns A promise resolving to the notification event, or null
*/
notify(config: NotificationConfig | string): Promise<Event> | null
// --- Clipboard ---
/**
* Copy a uibuilder variable's value to the clipboard.
* @param varToCopy The name of the uibuilder variable to copy
*/
copyToClipboard(varToCopy: string): void
}
/** The default uibuilder instance */
declare const uibuilder: Uib
export { uibuilder }
export default uibuilder