Skip to content

Commit

Permalink
Pkg mgt & route mgt rewritten. Some bugs fixed. Improved Edit panel. …
Browse files Browse the repository at this point in the history
…See changelog for details.
  • Loading branch information
TotallyInformation committed Oct 16, 2021
1 parent 9769b98 commit c26a9d4
Show file tree
Hide file tree
Showing 17 changed files with 4,215 additions and 4,079 deletions.
647 changes: 103 additions & 544 deletions CHANGELOG.md

Large diffs are not rendered by default.

582 changes: 582 additions & 0 deletions docs/CHANGELOG-v3-v4.md

Large diffs are not rendered by default.

67 changes: 64 additions & 3 deletions docs/package-management-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

This is usually `~/.node-red/` and is the main folder containing the settings and node packages for Node-RED.

Prio to v5 of uibuilder, this was used to install uibuilder front-end packages. As of v5+, this is no longer the case.
Prior to v5 of uibuilder, this was used to install uibuilder front-end packages. As of v5+, this is no longer the case.

### Folder: `<uibRoot>/`

Expand All @@ -15,14 +15,75 @@ Needs a package.json file and will contain a `node_modules` folder if any packag

This is the local configuration folder for a uibuilder node instance (as defined by the node's `url` setting).

Needs a `package.json` file and will contain a `node_modules` folder if any packages are installed for this uibuilder node instance.
Needs a `package.json` file but currently, packages installed here are only used for processing the front-end code. Normally, you would not see any `dependencies`, only `dev-dependencies` for example Webpack.

## System startup

When the uibuilder module is loaded, it immediately sets up the required web server routes. This includes the front-end library (vendor) routes for any packages installed to `uibRoot`.

```
uibuilder.js[Uib->runtimeSetup]->web.js[setup->_webSetup->serveVendorPackages, serveVendorSocketIo]
```

## Editor

### Open panel for a uibuilder node instance

On opening a uibuilder node configuration panel in the editor:

```
uibuilder.html[oneditprepare->packageList]->admin-api-v2.js[uibvendorpackages]->web.js[serveVendorPackages]
Sets the 'packages' variable.
```

On click on "Libraries" tab:

```
uibuilder.js[tabLibraries->]
```

### Add a new package

### Remove a package
### Remove a package

## Internal Variables

You don't need to know these unless you are working on the uibuilder code.

### `uibuilder.js`

#### `packageMgt.uibPackageJson`

Set by the `getUibRootPackageJson` method in `nodes/libs/package-mgt.js`.
It contains the contents of the `<uibRoot>/package.json` file.

The important properties are:

* `dependencies` - the list of installed packages that are served up by uibuilder
* `uibuilder` - Metadata and config data for the uibuilder module.

Specifically `uibuilder.packages` which contains the package metadata:

```json
{
"vue": {
"installFolder": "/src/uibRoot/node_modules/vue",
"installedVersion": "2.6.14",
"estimatedEntryPoint": "dist/vue.js",
"homepage": "https://github.com/vuejs/vue#readme",
"packageUrl": "/vue",
"url": "../uibuilder/vendor/vue/dist/vue.js",
"spec": "^2.6.14"
},
....
}
```

### `uibuilder.html`

#### `packages`

Set when panel is opened. See Editor section above.

A copy of the `uibuilder.packages` data shown above.
5 changes: 3 additions & 2 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/** Define Global RED object so that VScode typescript stops whinging. */

declare var RED:any;


/** Define Global RED object so that VScode typescript stops whinging. */

//import { EditorRED } from "node-red";

// declare global {
Expand Down
344 changes: 89 additions & 255 deletions nodes/libs/admin-api-v2.js

Large diffs are not rendered by default.

186 changes: 115 additions & 71 deletions nodes/libs/admin-api-v3.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,81 +194,122 @@ function adminRouterV3(uib, log) {
params.type = 'get'

// Commands ...
if ( params.cmd === 'listall' ) { // List all folders and files for this uibuilder instance
log.trace(`[uibuilder:admin-router:GET] Admin API. List all folders and files. url=${params.url}, root fldr=${uib.rootFolder}`)

// get list of all (sub)folders (follow symlinks as well)
const out = {'root':[]}
const root2 = uib.rootFolder.replace(/\\/g, '/')
fg.stream([`${root2}/${params.url}/**`], { dot: true, onlyFiles: false, deep: 10, followSymbolicLinks: true, markDirectories: true })
.on('data', entry => {
entry = entry.replace(`${root2}/${params.url}/`, '')
let fldr
if ( entry.endsWith('/') ) {
// remove trailing /
fldr = entry.slice(0, -1)
// For the root folder of the instance, use "root" as the name (matches editor processing)
if ( fldr === '' ) fldr = 'root'
out[fldr] = []
} else {
let splitEntry = entry.split('/')
let last = splitEntry.pop()
fldr = splitEntry.join('/')
if ( fldr === '' ) fldr = 'root'
out[fldr].push(last)
switch (params.cmd) {
// List all folders and files for this uibuilder instance
case 'listall': {
log.trace(`[uibuilder:admin-router:GET] Admin API. List all folders and files. url=${params.url}, root fldr=${uib.rootFolder}`)

// get list of all (sub)folders (follow symlinks as well)
const out = {'root':[]}
const root2 = uib.rootFolder.replace(/\\/g, '/')
fg.stream([`${root2}/${params.url}/**`], { dot: true, onlyFiles: false, deep: 10, followSymbolicLinks: true, markDirectories: true })
.on('data', entry => {
entry = entry.replace(`${root2}/${params.url}/`, '')
let fldr
if ( entry.endsWith('/') ) {
// remove trailing /
fldr = entry.slice(0, -1)
// For the root folder of the instance, use "root" as the name (matches editor processing)
if ( fldr === '' ) fldr = 'root'
out[fldr] = []
} else {
let splitEntry = entry.split('/')
let last = splitEntry.pop()
fldr = splitEntry.join('/')
if ( fldr === '' ) fldr = 'root'
out[fldr].push(last)
}
})
.on('end', () => {
res.statusMessage = 'Folders and Files listed successfully'
res.status(200).json(out)
})

break
} // -- end of listall -- //

// Check if URL is already in use
case 'checkurls': {
log.trace(`[uibuilder:admin-router:GET:checkurls] Check if URL is already in use. URL: ${params.url}`)

/** @returns {boolean} True if the given url exists, else false */
let chkInstances = Object.values(uib.instances).includes(params.url)
let chkFolders = fs.existsSync(path.join(uib.rootFolder, params.url))

res.statusMessage = 'Instances and Folders checked'
res.status(200).json( chkInstances || chkFolders )

break
} // -- end of checkurls -- //

// List all of the deployed instance urls
case 'listinstances': {

log.trace('[uibuilder:admin-router:GET:listinstances] Returning a list of deployed URLs (instances of uib).')

/** @returns {boolean} True if the given url exists, else false */
// let chkInstances = Object.values(uib.instances).includes(params.url)
// let chkFolders = fs.existsSync(path.join(uib.rootFolder, params.url))

res.statusMessage = 'Instances listed'
res.status(200).json( uib.instances )

break
} // -- end of listinstances -- //

// Return a list of all user urls in use by ExpressJS
case 'listurls': {
// TODO Not currently working
var route, routes = []
web.app._router.stack.forEach( (middleware) => {
if(middleware.route){ // routes registered directly on the app
let path = middleware.route.path
let methods = middleware.route.methods
routes.push({path: path, methods: methods})
} else if(middleware.name === 'router'){ // router middleware
middleware.handle.stack.forEach(function(handler){
route = handler.route
route && routes.push(route)
})
}
})
.on('end', () => {
res.statusMessage = 'Folders and Files listed successfully'
res.status(200).json(out)
})
// -- end of listall -- //
} else if ( params.cmd === 'checkurls' ) { // Check if URL is already in use
log.trace(`[uibuilder:admin-router:GET:checkurls] Check if URL is already in use. URL: ${params.url}`)

/** @returns {boolean} True if the given url exists, else false */
let chkInstances = Object.values(uib.instances).includes(params.url)
let chkFolders = fs.existsSync(path.join(uib.rootFolder, params.url))
console.log(web.app._router.stack[0])

log.trace('[uibuilder:admin-router:GET:listurls] Admin API. List of all user urls in use.')
res.statusMessage = 'URLs listed successfully'
//res.status(200).json(routes)
res.status(200).json(web.app._router.stack)

break
} // -- end of listurls -- //

// See if a node's custom folder exists. Return true if it does, else false
case 'checkfolder': {
log.trace(`[uibuilder:admin-router:GET:checkfolder] See if a node's custom folder exists. URL: ${params.url}`)

res.statusMessage = 'Instances and Folders checked'
res.status(200).json( chkInstances || chkFolders )
// -- end of checkurls -- //
} else if ( params.cmd === 'listinstances' ) { // List all of the deployed instance urls
const folder = path.join( uib.rootFolder, params.url)

log.trace('[uibuilder:admin-router:GET:listinstances] Returning a list of deployed URLs (instances of uib).')

/** @returns {boolean} True if the given url exists, else false */
// let chkInstances = Object.values(uib.instances).includes(params.url)
// let chkFolders = fs.existsSync(path.join(uib.rootFolder, params.url))

res.statusMessage = 'Instances listed'
res.status(200).json( uib.instances )

} else if ( params.cmd === 'listurls' ) { // Return a list of all user urls in use by ExpressJS
// TODO Not currently working
var route, routes = []
web.app._router.stack.forEach( (middleware) => {
if(middleware.route){ // routes registered directly on the app
let path = middleware.route.path
let methods = middleware.route.methods
routes.push({path: path, methods: methods})
} else if(middleware.name === 'router'){ // router middleware
middleware.handle.stack.forEach(function(handler){
route = handler.route
route && routes.push(route)
fs.access(folder, fs.constants.F_OK)
.then( () => {
res.statusMessage = 'Folder checked'
res.status(200).json( true )
return true
})
}
})
console.log(web.app._router.stack[0])

log.trace('[uibuilder:admin-router:GET:listurls] Admin API. List of all user urls in use.')
res.statusMessage = 'URLs listed successfully'
//res.status(200).json(routes)
res.status(200).json(web.app._router.stack)
// -- end of listurls -- //
.catch( () => { //err) => {
res.statusMessage = 'Folder checked'
res.status(200).json( false )
return false
})

break
} // -- end of checkfolder -- //

default: {
break
}
}

})

// TODO Write file contents
.put(function(/** @type {express.Request} */ req, /** @type {express.Response} */ res) {
// @ts-ignore
Expand All @@ -294,6 +335,7 @@ function adminRouterV3(uib, log) {
})

})

// Load new template or Create a new folder or file
.post(function(/** @type {express.Request} */ req, /** @type {express.Response} */ res) {
// @ts-ignore
Expand Down Expand Up @@ -390,6 +432,7 @@ function adminRouterV3(uib, log) {
} // end of else

}) // --- End of POST processing --- //

// Delete a folder or a file
.delete(function(/** @type {express.Request} */ req, /** @type {express.Response} */ res) {
// @ts-ignore ts(2339)
Expand Down Expand Up @@ -459,9 +502,10 @@ function adminRouterV3(uib, log) {
'params': params,
})
})
/** @see https://expressjs.com/en/4x/api.html#app.METHOD for other methods
* patch, report, search ?
*/

/** @see https://expressjs.com/en/4x/api.html#app.METHOD for other methods
* patch, report, search ?
*/

return admin_Router_V3
}
Expand Down
Loading

0 comments on commit c26a9d4

Please sign in to comment.