allow usercode to define config and set capabilities
This commit is contained in:
parent
39bc516def
commit
e3d4ac5eba
|
@ -91,31 +91,57 @@ def writeFileIfChanged(fileName,data):
|
||||||
with open(fileName,"w") as oh:
|
with open(fileName,"w") as oh:
|
||||||
oh.write(data)
|
oh.write(data)
|
||||||
|
|
||||||
def generateCfg(ch,oh,inFile=''):
|
def mergeConfig(base,other):
|
||||||
config=json.load(ch)
|
for bdir in other:
|
||||||
oh.write("//generated from %s\n"%inFile)
|
cname=os.path.join(bdir,"config.json")
|
||||||
oh.write('#include "GwConfigItem.h"\n')
|
if os.path.exists(cname):
|
||||||
l=len(config)
|
print("merge config %s"%cname)
|
||||||
oh.write('class GwConfigDefinitions{\n')
|
with open(cname,'rb') as ah:
|
||||||
oh.write(' public:\n')
|
merge=json.load(ah)
|
||||||
oh.write(' int getNumConfig() const{return %d;}\n'%(l))
|
base=base+merge
|
||||||
for item in config:
|
return base
|
||||||
n=item.get('name')
|
|
||||||
if n is None:
|
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||||
continue
|
if not os.path.exists(inFile):
|
||||||
if len(n) > 15:
|
raise Exception("unable to read cfg file %s"%inFile)
|
||||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
data=""
|
||||||
oh.write(' const String %s=F("%s");\n'%(n,n))
|
with open(inFile,'rb') as ch:
|
||||||
oh.write(' protected:\n')
|
config=json.load(ch)
|
||||||
oh.write(' GwConfigItem *configs[%d]={\n'%(l))
|
config=mergeConfig(config,addDirs)
|
||||||
first=True
|
data=json.dumps(config,indent=2)
|
||||||
for item in config:
|
writeFileIfChanged(outFile,data)
|
||||||
if not first:
|
|
||||||
oh.write(',\n')
|
def generateCfg(inFile,outFile,addDirs=[]):
|
||||||
first=False
|
if not os.path.exists(inFile):
|
||||||
oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default')))
|
raise Exception("unable to read cfg file %s"%inFile)
|
||||||
oh.write('};\n')
|
data=""
|
||||||
oh.write('};\n')
|
with open(inFile,'rb') as ch:
|
||||||
|
config=json.load(ch)
|
||||||
|
config=mergeConfig(config,addDirs)
|
||||||
|
data+="//generated from %s\n"%inFile
|
||||||
|
data+='#include "GwConfigItem.h"\n'
|
||||||
|
l=len(config)
|
||||||
|
data+='class GwConfigDefinitions{\n'
|
||||||
|
data+=' public:\n'
|
||||||
|
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||||
|
for item in config:
|
||||||
|
n=item.get('name')
|
||||||
|
if n is None:
|
||||||
|
continue
|
||||||
|
if len(n) > 15:
|
||||||
|
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||||
|
data+=' const String %s=F("%s");\n'%(n,n)
|
||||||
|
data+=' protected:\n'
|
||||||
|
data+=' GwConfigItem *configs[%d]={\n'%(l)
|
||||||
|
first=True
|
||||||
|
for item in config:
|
||||||
|
if not first:
|
||||||
|
data+=',\n'
|
||||||
|
first=False
|
||||||
|
data+=" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default'))
|
||||||
|
data+='};\n'
|
||||||
|
data+='};\n'
|
||||||
|
writeFileIfChanged(outFile,data)
|
||||||
|
|
||||||
|
|
||||||
def generateXdrMappings(fp,oh,inFile=''):
|
def generateXdrMappings(fp,oh,inFile=''):
|
||||||
|
@ -157,10 +183,17 @@ def generateXdrMappings(fp,oh,inFile=''):
|
||||||
oh.write("\n")
|
oh.write("\n")
|
||||||
oh.write("};\n")
|
oh.write("};\n")
|
||||||
|
|
||||||
def genereateUserTasks(outfile):
|
userTaskDirs=[]
|
||||||
includes=[]
|
|
||||||
|
def getUserTaskDirs():
|
||||||
|
rt=[]
|
||||||
taskdirs=glob.glob(os.path.join('lib','*task*'))
|
taskdirs=glob.glob(os.path.join('lib','*task*'))
|
||||||
for task in taskdirs:
|
for task in taskdirs:
|
||||||
|
rt.append(task)
|
||||||
|
return rt
|
||||||
|
def genereateUserTasks(outfile):
|
||||||
|
includes=[]
|
||||||
|
for task in userTaskDirs:
|
||||||
#print("##taskdir=%s"%task)
|
#print("##taskdir=%s"%task)
|
||||||
base=os.path.basename(task)
|
base=os.path.basename(task)
|
||||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||||
|
@ -201,9 +234,11 @@ def getContentType(fn):
|
||||||
return "application/octet-stream"
|
return "application/octet-stream"
|
||||||
|
|
||||||
def prebuild(env):
|
def prebuild(env):
|
||||||
|
global userTaskDirs
|
||||||
print("#prebuild running")
|
print("#prebuild running")
|
||||||
if not checkDir():
|
if not checkDir():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
userTaskDirs=getUserTaskDirs()
|
||||||
embedded=getEmbeddedFiles(env)
|
embedded=getEmbeddedFiles(env)
|
||||||
filedefs=[]
|
filedefs=[]
|
||||||
for ef in embedded:
|
for ef in embedded:
|
||||||
|
@ -223,7 +258,10 @@ def prebuild(env):
|
||||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||||
generateFile(os.path.join(basePath(),CFG_FILE),os.path.join(outPath(),CFG_INCLUDE),generateCfg)
|
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||||
|
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||||
|
compressFile(mergedConfig,mergedConfig+".gz")
|
||||||
|
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE))
|
||||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||||
|
|
|
@ -19,4 +19,7 @@ class GwApi{
|
||||||
#ifndef DECLARE_USERTASK
|
#ifndef DECLARE_USERTASK
|
||||||
#define DECLARE_USERTASK(task)
|
#define DECLARE_USERTASK(task)
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef DECLARE_CAPABILITY
|
||||||
|
#define DECLARE_CAPABILITY(name,value)
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -35,10 +35,15 @@ class GetBoatDataRequest: public GwMessage{
|
||||||
void exampleTask(void *param){
|
void exampleTask(void *param){
|
||||||
GwApi *api=(GwApi*)param;
|
GwApi *api=(GwApi*)param;
|
||||||
GwLog *logger=api->getLogger();
|
GwLog *logger=api->getLogger();
|
||||||
|
//get some configuration data
|
||||||
|
bool exampleSwitch=api->getConfig()->getConfigItem(
|
||||||
|
api->getConfig()->exampleConfig,
|
||||||
|
true)->asBoolean();
|
||||||
//------
|
//------
|
||||||
//initialization goes here
|
//initialization goes here
|
||||||
//------
|
//------
|
||||||
bool hasPosition=false;
|
bool hasPosition=false;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"example switch ist %s",exampleSwitch?"true":"false");
|
||||||
while(true){
|
while(true){
|
||||||
delay(1000);
|
delay(1000);
|
||||||
/*
|
/*
|
||||||
|
@ -65,14 +70,14 @@ void exampleTask(void *param){
|
||||||
}
|
}
|
||||||
if (r->latitude == INVALID_COORD || r->longitude == INVALID_COORD){
|
if (r->latitude == INVALID_COORD || r->longitude == INVALID_COORD){
|
||||||
if (hasPosition){
|
if (hasPosition){
|
||||||
logger->logDebug(GwLog::ERROR,"position lost...");
|
if (exampleSwitch) logger->logDebug(GwLog::ERROR,"position lost...");
|
||||||
hasPosition=false;
|
hasPosition=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
//do something with the data we have from boatData
|
//do something with the data we have from boatData
|
||||||
if (! hasPosition){
|
if (! hasPosition){
|
||||||
logger->logDebug(GwLog::LOG,"postion now available lat=%f, lon=%f",
|
if (exampleSwitch) logger->logDebug(GwLog::LOG,"postion now available lat=%f, lon=%f",
|
||||||
r->latitude,r->longitude);
|
r->latitude,r->longitude);
|
||||||
hasPosition=true;
|
hasPosition=true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,9 @@
|
||||||
void exampleTask(void *param);
|
void exampleTask(void *param);
|
||||||
//make the task known to the core
|
//make the task known to the core
|
||||||
DECLARE_USERTASK(exampleTask);
|
DECLARE_USERTASK(exampleTask);
|
||||||
|
//we declare a capability that we can
|
||||||
|
//use in config.json to only show some
|
||||||
|
//elements when this capability is set correctly
|
||||||
|
DECLARE_CAPABILITY(testboard,true);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
|
@ -1,8 +1,9 @@
|
||||||
Extending the Core
|
Extending the Core
|
||||||
==================
|
==================
|
||||||
This directory contains an example on how you can extend the base functionality of the gateway.
|
This directory contains an example on how you can extend the base functionality of the gateway.
|
||||||
Basically you can define own boards here and can add one or more tasks that will be started by the core.
|
Maybe you have another interesting hardware or need some additional functions but would like to use the base functionality of the gateway.
|
||||||
You can also add additional libraries that will be used for your task.
|
You can define own hardware configurations (environments) here and can add one or more tasks that will be started by the core.
|
||||||
|
You can also add additional libraries that will be used to build your task.
|
||||||
In this example we define an addtional board (environment) with the name "testboard".
|
In this example we define an addtional board (environment) with the name "testboard".
|
||||||
When building for this board we add the -DTEST_BOARD to the compilation - see [platformio.ini](platformio.ini).
|
When building for this board we add the -DTEST_BOARD to the compilation - see [platformio.ini](platformio.ini).
|
||||||
The additional task that we defined will only be compiled and started for this environment (see the #ifdef TEST_BOARD in the code).
|
The additional task that we defined will only be compiled and started for this environment (see the #ifdef TEST_BOARD in the code).
|
||||||
|
@ -10,22 +11,41 @@ You can add your own directory below "lib". The name of the directory must conta
|
||||||
|
|
||||||
Files
|
Files
|
||||||
-----
|
-----
|
||||||
* [platformio.ini](platformio.ini)
|
* [platformio.ini](platformio.ini)<br>
|
||||||
extend the base configuration - we add a dummy library here and define our buil environment (board)
|
This file is completely optional.
|
||||||
* [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core (DECLARE_USERTASK in the code).
|
You only need this if you want to
|
||||||
|
extend the base configuration - we add a dummy library here and define one additional build environment (board)
|
||||||
|
* [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core (DECLARE_USERTASK in the code). Optionally it can define some capabilities (using DECLARE_CAPABILITY) that can be used in the config UI (see below).
|
||||||
|
Avoid including headers from other libraries in this file as this could interfere with the main code. Just only include them in your .cpp files (or in other headers).
|
||||||
* [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code.
|
* [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code.
|
||||||
|
We can have as many cpp (and header files) as we need to structure our code.
|
||||||
* [GwExampleHardware.h](GwExampleHardware.h) includes our pin definitions for the board.
|
* [GwExampleHardware.h](GwExampleHardware.h) includes our pin definitions for the board.
|
||||||
|
* [config.json](config.json)<br>
|
||||||
|
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
||||||
|
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
||||||
|
|
||||||
Hints
|
Hints
|
||||||
-----
|
-----
|
||||||
Just be careful not to interfere with names from the core - so it is a good practice to prefix your files and class like in the example.
|
Just be careful not to interfere with C symbols from the core - so it is a good practice to prefix your files and class like in the example.
|
||||||
|
|
||||||
Developing
|
Developing
|
||||||
----------
|
----------
|
||||||
To develop I recommend forking the gateway repository and adding your own directory below lib (with the string task in it's name).
|
To develop I recommend forking the gateway repository and adding your own directory below lib (with the string task in it's name).
|
||||||
As your code goes into a separate directory it should be very easy to fetch upstream changes without the need to adapt your code.
|
As your code goes into a separate directory it should be very easy to fetch upstream changes without the need to adapt your code.
|
||||||
|
Typically after forking the repo on github (https://github.com/wellenvogel/esp32-nmea2000) and initially cloning it you will add my repository as an "upstream repo":
|
||||||
|
```
|
||||||
|
git remote add upstream https://github.com/wellenvogel/esp32-nmea2000.git
|
||||||
|
```
|
||||||
|
To merge in a new version use:
|
||||||
|
```
|
||||||
|
git fetch upstream
|
||||||
|
git merge upstream/master
|
||||||
|
```
|
||||||
|
Refer to https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
|
||||||
|
|
||||||
|
By following the hints in this doc the merge should always succeed without conflicts.
|
||||||
|
|
||||||
Future Plans
|
Future Plans
|
||||||
------------
|
------------
|
||||||
If there will be a need we can extend this extension API by means of adding config items and specific java script code and css for the UI.
|
If there will be a need we can extend this extension API by means of adding specific java script code and css for the UI.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "exampleConfig",
|
||||||
|
"label": "logging on",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "false",
|
||||||
|
"description": "switch on logging of position acquired/failed",
|
||||||
|
"category": "example",
|
||||||
|
"capabilities": {
|
||||||
|
"testboard":"true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
#include "GwUserCode.h"
|
#include "GwUserCode.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
//user task handling
|
//user task handling
|
||||||
class UserTask{
|
class UserTask{
|
||||||
public:
|
public:
|
||||||
|
@ -11,7 +12,9 @@ class UserTask{
|
||||||
this->task=task;
|
this->task=task;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<UserTask> userTasks;
|
std::vector<UserTask> userTasks;
|
||||||
|
GwUserCode::Capabilities userCapabilities;
|
||||||
|
|
||||||
void registerUserTask(TaskFunction_t task,String name){
|
void registerUserTask(TaskFunction_t task,String name){
|
||||||
userTasks.push_back(UserTask(name,task));
|
userTasks.push_back(UserTask(name,task));
|
||||||
|
@ -23,7 +26,14 @@ class GwUserTask{
|
||||||
registerUserTask(task,name);
|
registerUserTask(task,name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class GwUserCapability{
|
||||||
|
public:
|
||||||
|
GwUserCapability(String name,String value){
|
||||||
|
userCapabilities[name]=value;
|
||||||
|
}
|
||||||
|
};
|
||||||
#define DECLARE_USERTASK(task) GwUserTask __##task##__(task,#task);
|
#define DECLARE_USERTASK(task) GwUserTask __##task##__(task,#task);
|
||||||
|
#define DECLARE_CAPABILITY(name,value) GwUserCapability __CAP##name__(#name,#value);
|
||||||
#include "GwUserTasks.h"
|
#include "GwUserTasks.h"
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
class TaskApi : public GwApi
|
class TaskApi : public GwApi
|
||||||
|
@ -87,3 +97,7 @@ void GwUserCode::startAddonTask(String name, TaskFunction_t task, int id){
|
||||||
LOG_DEBUG(GwLog::LOG,"starting addon task %s with id %d",name.c_str(),id);
|
LOG_DEBUG(GwLog::LOG,"starting addon task %s with id %d",name.c_str(),id);
|
||||||
startAddOnTask(api,task,id);
|
startAddOnTask(api,task,id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GwUserCode::Capabilities * GwUserCode::getCapabilities(){
|
||||||
|
return &userCapabilities;
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
#ifndef _GWUSERCODE_H
|
#ifndef _GWUSERCODE_H
|
||||||
#define _GWUSERCODE_H
|
#define _GWUSERCODE_H
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <map>
|
||||||
class GwLog;
|
class GwLog;
|
||||||
class GwApi;
|
class GwApi;
|
||||||
class GwUserCode{
|
class GwUserCode{
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
GwApi *api;
|
GwApi *api;
|
||||||
public:
|
public:
|
||||||
|
typedef std::map<String,String> Capabilities;
|
||||||
GwUserCode(GwApi *api);
|
GwUserCode(GwApi *api);
|
||||||
void startUserTasks(int baseId);
|
void startUserTasks(int baseId);
|
||||||
void startAddonTask(String name,TaskFunction_t task, int id);
|
void startAddonTask(String name,TaskFunction_t task, int id);
|
||||||
|
Capabilities *getCapabilities();
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
15
src/main.cpp
15
src/main.cpp
|
@ -265,6 +265,7 @@ bool delayedRestart(){
|
||||||
},"reset",1000,&logger,0,NULL) == pdPASS;
|
},"reset",1000,&logger,0,NULL) == pdPASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GwUserCode userCodeHandler(new ApiImpl(200));
|
||||||
|
|
||||||
#define JSON_OK "{\"status\":\"OK\"}"
|
#define JSON_OK "{\"status\":\"OK\"}"
|
||||||
|
|
||||||
|
@ -329,7 +330,12 @@ class CapabilitiesRequest : public GwRequestMessage{
|
||||||
CapabilitiesRequest() : GwRequestMessage(F("application/json"),F("capabilities")){};
|
CapabilitiesRequest() : GwRequestMessage(F("application/json"),F("capabilities")){};
|
||||||
protected:
|
protected:
|
||||||
virtual void processRequest(){
|
virtual void processRequest(){
|
||||||
DynamicJsonDocument json(JSON_OBJECT_SIZE(6));
|
int numCapabilities=userCodeHandler.getCapabilities()->size();
|
||||||
|
DynamicJsonDocument json(JSON_OBJECT_SIZE(numCapabilities*3+6));
|
||||||
|
for (auto it=userCodeHandler.getCapabilities()->begin();
|
||||||
|
it != userCodeHandler.getCapabilities()->end();it++){
|
||||||
|
json[it->first]=it->second;
|
||||||
|
}
|
||||||
#ifdef GWSERIAL_MODE
|
#ifdef GWSERIAL_MODE
|
||||||
String serial(F(GWSERIAL_MODE));
|
String serial(F(GWSERIAL_MODE));
|
||||||
#else
|
#else
|
||||||
|
@ -692,11 +698,10 @@ void setup() {
|
||||||
NMEA2000.Open();
|
NMEA2000.Open();
|
||||||
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
||||||
logger.flush();
|
logger.flush();
|
||||||
GwUserCode userHandler(new ApiImpl(200));
|
userCodeHandler.startAddonTask(F("handleButtons"),handleButtons,100);
|
||||||
userHandler.startAddonTask(F("handleButtons"),handleButtons,100);
|
|
||||||
setLedMode(LED_GREEN);
|
setLedMode(LED_GREEN);
|
||||||
userHandler.startAddonTask(F("handleLeds"),handleLeds,101);
|
userCodeHandler.startAddonTask(F("handleLeds"),handleLeds,101);
|
||||||
userHandler.startUserTasks(200);
|
userCodeHandler.startUserTasks(200);
|
||||||
|
|
||||||
logger.logDebug(GwLog::LOG,"setup done");
|
logger.logDebug(GwLog::LOG,"setup done");
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,26 +49,31 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
path = urllib.parse.unquote(path)
|
path = urllib.parse.unquote(path)
|
||||||
path = posixpath.normpath(path)
|
path = posixpath.normpath(path)
|
||||||
words = path.split('/')
|
words = path.split('/')
|
||||||
words = filter(None, words)
|
words = list(filter(None, words))
|
||||||
path = self.server.baseDir
|
for baseDir in [
|
||||||
for word in words:
|
os.path.join(self.server.baseDir,'lib','generated'),
|
||||||
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
os.path.join(self.server.baseDir,'web')]:
|
||||||
# Ignore components that are not a simple file/directory name
|
rpath = baseDir
|
||||||
continue
|
for word in words:
|
||||||
path = os.path.join(path, word)
|
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
||||||
if trailing_slash:
|
# Ignore components that are not a simple file/directory name
|
||||||
path += '/'
|
continue
|
||||||
return path
|
rpath = os.path.join(rpath, word)
|
||||||
|
if trailing_slash:
|
||||||
|
rpath += '/'
|
||||||
|
if os.path.exists(rpath):
|
||||||
|
return rpath
|
||||||
|
|
||||||
def run(baseDir,port,apiUrl,server_class=http.server.HTTPServer, handler_class=RequestHandler):
|
def run(port,apiUrl,server_class=http.server.HTTPServer, handler_class=RequestHandler):
|
||||||
|
basedir=os.path.join(os.path.dirname(__file__),'..')
|
||||||
server_address = ('', port)
|
server_address = ('', port)
|
||||||
httpd = server_class(server_address, handler_class)
|
httpd = server_class(server_address, handler_class)
|
||||||
httpd.proxyUrl=apiUrl
|
httpd.proxyUrl=apiUrl
|
||||||
httpd.baseDir=baseDir
|
httpd.baseDir=basedir
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) != 4:
|
if len(sys.argv) != 3:
|
||||||
print("usage: %s basedir port apiurl"%sys.argv[0])
|
print("usage: %s port apiurl"%sys.argv[0])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
run(sys.argv[1],int(sys.argv[2]),sys.argv[3])
|
run(int(sys.argv[1]),sys.argv[2])
|
99
web/index.js
99
web/index.js
|
@ -829,10 +829,12 @@ function toggleClass(el,id,classList){
|
||||||
function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
let category;
|
let category;
|
||||||
let categoryEl;
|
let categoryEl;
|
||||||
|
let categoryFrame;
|
||||||
let frame = parent.querySelector('.configFormRows');
|
let frame = parent.querySelector('.configFormRows');
|
||||||
if (!frame) throw Error("no config form");
|
if (!frame) throw Error("no config form");
|
||||||
frame.innerHTML = '';
|
frame.innerHTML = '';
|
||||||
configDefinitions = defs;
|
configDefinitions = defs;
|
||||||
|
let currentCategoryPopulated=true;
|
||||||
defs.forEach(function (item) {
|
defs.forEach(function (item) {
|
||||||
if (!item.type) return;
|
if (!item.type) return;
|
||||||
if (item.category.match(/^xdr/)){
|
if (item.category.match(/^xdr/)){
|
||||||
|
@ -842,7 +844,11 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
if(includeXdr) return;
|
if(includeXdr) return;
|
||||||
}
|
}
|
||||||
if (item.category != category || !categoryEl) {
|
if (item.category != category || !categoryEl) {
|
||||||
let categoryFrame = addEl('div', 'category', frame);
|
if (categoryFrame && ! currentCategoryPopulated){
|
||||||
|
categoryFrame.remove();
|
||||||
|
}
|
||||||
|
currentCategoryPopulated=false;
|
||||||
|
categoryFrame = addEl('div', 'category', frame);
|
||||||
categoryFrame.setAttribute('data-category',item.category)
|
categoryFrame.setAttribute('data-category',item.category)
|
||||||
let categoryTitle = addEl('div', 'title', categoryFrame);
|
let categoryTitle = addEl('div', 'title', categoryFrame);
|
||||||
let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
|
let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
|
||||||
|
@ -862,60 +868,67 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
})
|
})
|
||||||
category = item.category;
|
category = item.category;
|
||||||
}
|
}
|
||||||
|
let showItem=true;
|
||||||
if (item.capabilities !== undefined) {
|
if (item.capabilities !== undefined) {
|
||||||
for (let capability in item.capabilities) {
|
for (let capability in item.capabilities) {
|
||||||
let values = item.capabilities[capability];
|
let values = item.capabilities[capability];
|
||||||
if (!capabilities[capability]) return;
|
|
||||||
let found = false;
|
let found = false;
|
||||||
|
if (! (values instanceof Array)) values=[values];
|
||||||
values.forEach(function (v) {
|
values.forEach(function (v) {
|
||||||
if (capabilities[capability] == v) found = true;
|
if (capabilities[capability] == v) found = true;
|
||||||
});
|
});
|
||||||
if (!found) return;
|
if (!found) showItem=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let row = addEl('div', 'row', categoryEl);
|
if (showItem) {
|
||||||
let label = item.label || item.name;
|
currentCategoryPopulated=true;
|
||||||
addEl('span', 'label', row, label);
|
let row = addEl('div', 'row', categoryEl);
|
||||||
let valueFrame = addEl('div', 'value', row);
|
let label = item.label || item.name;
|
||||||
let valueEl = createInput(item, valueFrame);
|
addEl('span', 'label', row, label);
|
||||||
if (!valueEl) return;
|
let valueFrame = addEl('div', 'value', row);
|
||||||
valueEl.setAttribute('data-default', item.default);
|
let valueEl = createInput(item, valueFrame);
|
||||||
valueEl.addEventListener('change', function (ev) {
|
if (!valueEl) return;
|
||||||
let el = ev.target;
|
valueEl.setAttribute('data-default', item.default);
|
||||||
checkChange(el, row,item.name);
|
valueEl.addEventListener('change', function (ev) {
|
||||||
})
|
let el = ev.target;
|
||||||
let condition=getConditions(item.name);
|
checkChange(el, row, item.name);
|
||||||
if (condition){
|
|
||||||
condition.forEach(function(cel){
|
|
||||||
for (let c in cel){
|
|
||||||
if (!conditionRelations[c]){
|
|
||||||
conditionRelations[c]=[];
|
|
||||||
}
|
|
||||||
conditionRelations[c].push(valueEl);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
let condition = getConditions(item.name);
|
||||||
if (item.check) valueEl.setAttribute('data-check', item.check);
|
if (condition) {
|
||||||
let btContainer = addEl('div', 'buttonContainer', row);
|
condition.forEach(function (cel) {
|
||||||
let bt = addEl('button', 'defaultButton', btContainer, 'X');
|
for (let c in cel) {
|
||||||
bt.setAttribute('data-default', item.default);
|
if (!conditionRelations[c]) {
|
||||||
bt.addEventListener('click', function (ev) {
|
conditionRelations[c] = [];
|
||||||
valueEl.value = valueEl.getAttribute('data-default');
|
}
|
||||||
let changeEvent = new Event('change');
|
conditionRelations[c].push(valueEl);
|
||||||
valueEl.dispatchEvent(changeEvent);
|
}
|
||||||
})
|
})
|
||||||
bt = addEl('button', 'infoButton', btContainer, '?');
|
|
||||||
bt.addEventListener('click', function (ev) {
|
|
||||||
if (item.description){
|
|
||||||
showOverlay(item.description);
|
|
||||||
}
|
}
|
||||||
else{
|
if (item.check) valueEl.setAttribute('data-check', item.check);
|
||||||
if (item.category.match(/^xdr/)){
|
let btContainer = addEl('div', 'buttonContainer', row);
|
||||||
showXdrHelp();
|
let bt = addEl('button', 'defaultButton', btContainer, 'X');
|
||||||
|
bt.setAttribute('data-default', item.default);
|
||||||
|
bt.addEventListener('click', function (ev) {
|
||||||
|
valueEl.value = valueEl.getAttribute('data-default');
|
||||||
|
let changeEvent = new Event('change');
|
||||||
|
valueEl.dispatchEvent(changeEvent);
|
||||||
|
})
|
||||||
|
bt = addEl('button', 'infoButton', btContainer, '?');
|
||||||
|
bt.addEventListener('click', function (ev) {
|
||||||
|
if (item.description) {
|
||||||
|
showOverlay(item.description);
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
});
|
if (item.category.match(/^xdr/)) {
|
||||||
})
|
showXdrHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (categoryFrame && ! currentCategoryPopulated){
|
||||||
|
categoryFrame.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function loadConfigDefinitions() {
|
function loadConfigDefinitions() {
|
||||||
getJson("api/capabilities")
|
getJson("api/capabilities")
|
||||||
|
|
Loading…
Reference in New Issue