From 89080f2d2772a1f6e35a77143a98b405d1ef19fb Mon Sep 17 00:00:00 2001 From: CypressXt Date: Fri, 12 Apr 2024 03:13:49 +0200 Subject: [PATCH] Add support for env variable & add ex docker stack --- Dockerfile | 6 + WebMap.json | 517 ++++++++++++++++++++++++++++++++++++++++++++- docker-compose.yml | 15 ++ 3 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1cd0cf7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +from nodered/node-red:latest-minimal + +RUN npm install node-red-contrib-web-worldmap node-red-contrib-config + +COPY WebMap.json /data/WebMap.json +ENV FLOWS /data/WebMap.json diff --git a/WebMap.json b/WebMap.json index a6a6d35..bc45063 100644 --- a/WebMap.json +++ b/WebMap.json @@ -1 +1,516 @@ -[{"id":"db2b165142e8c36a","type":"tab","label":"web Map 2","disabled":false,"info":"Gets the flow of COT from FTS and displays on a web map (credit @ampledata#8354).\n The FTH WebMap integration can be installed as part of FTS UI or a stand alone, e.g. for a dashboard. ","env":[]},{"id":"9946c2419fd3c3ae","type":"inject","z":"db2b165142e8c36a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"120","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":160,"wires":[["33593e8ece650844"]]},{"id":"33593e8ece650844","type":"function","z":"db2b165142e8c36a","name":"CoT Ping","func":" msg.host = global.get('FTH_FTS_URL');\n msg.port = global.get('FTH_FTS_TCP_Port');\nconst dt = Date.now();\nconst dtD = new Date(dt).toISOString();\nconst dtD5 = new Date(dt + 250000).toISOString();\n\nmsg.payload = {\n event: {\n \"$\": { \n version: \"2.0\", \n type: \"t-x-d-d\", \n uid : \"node-red\",\n time: dtD,\n start: dtD,\n stale: dtD5,\n how: \"m-g\"\n }\n }\n \n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":160,"wires":[["7a0f7d3e4099e946"]]},{"id":"7a0f7d3e4099e946","type":"xml","z":"db2b165142e8c36a","name":"","property":"payload","attr":"","chr":"","x":450,"y":160,"wires":[["52446a6c25605721"]]},{"id":"7c52084b496b83bc","type":"comment","z":"db2b165142e8c36a","name":"Keep Server Connection Alive","info":"","x":200,"y":120,"wires":[]},{"id":"52446a6c25605721","type":"tcp request","z":"db2b165142e8c36a","server":"","port":"","out":"sit","ret":"buffer","splitc":" ","name":"FTS Server","x":590,"y":160,"wires":[["3c62054705949d69","4134eaa89c5c71e0"]]},{"id":"3c62054705949d69","type":"function","z":"db2b165142e8c36a","name":"buf->str","func":"/*\nlet oldPayload = msg.payload;\nmsg.payload = oldPayload.toString();\nreturn msg;\n*/\n\nvar rest = context.rest || \"\";\nvar p = rest + msg.payload.toString();\nvar b = p.split(\"\");\nwhile (b.length > 1) {\n var m = b.shift();\n node.send({ payload: m + \"\" });\n}\ncontext.rest = b;\nreturn;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":160,"wires":[["91213e2448bab185"]]},{"id":"91213e2448bab185","type":"xml","z":"db2b165142e8c36a","name":"","property":"payload","attr":"","chr":"","x":870,"y":160,"wires":[["4b3df29696d9fc8f"]]},{"id":"05f4656c40958267","type":"function","z":"db2b165142e8c36a","name":"CoT to Map","func":"/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\n\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\nconsole.log(eventType);\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; } \n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\" }${et[4] || \"-\" }${et[5] || \"-\" }--------`;\nSIDC = SIDC.substr(0,12).toUpperCase();\n\n/* Now handle all the weird extra CoT ones... */\nif (eventType === \"a-h-X-i-o\") { \n console.log(\"XIO--->\",SIDC)\n SIDC = \"EHIP--------\" }\nelse if (eventType === \"a-h-X-i-m-d\") { \n console.log(\"XIMD--->\",SIDC)\n SIDC = \"EHNPBB------\" }\nelse if (eventType === \"a-h-X-i-g-e\") { \n console.log(\"XIGE--->\",SIDC)\n SIDC = \"EHNPAC------\" }\n\n\nconsole.log(SIDC);\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"]; \n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid && course.toString() !== \"0\") {\n bearing = course;\n }\n }\n\n let _speed = track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed.toString() !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = event[\"point\"][0][\"$\"][\"ce\"];\nif (ce.toString() != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\n\nlet lat = event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = event[\"point\"][0][\"$\"][\"hae\"];\n\nif (lat == 0 && lon == 0 && alt == 0) {\n console.log(callsign,\"is at null island.\",eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n SIDC: SIDC,\n ttl: 600,\n weblink: weblink,\n layer: eventType,\n options: {scale:0.8}\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":40,"wires":[[]]},{"id":"8c35a77a0d0d3f3d","type":"comment","z":"db2b165142e8c36a","name":"Connect to FTS","info":"","x":600,"y":120,"wires":[]},{"id":"586a2364ecb46529","type":"comment","z":"db2b165142e8c36a","name":"Convert to Worldmap Payload","info":"","x":1060,"y":120,"wires":[]},{"id":"885b808dd8a4895d","type":"debug","z":"db2b165142e8c36a","name":"TAK map output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":80,"wires":[]},{"id":"3a1205e793d055fa","type":"worldmap","z":"db2b165142e8c36a","name":"TAK Map","lat":"43","lon":"13","zoom":"4","layer":"EsriDG","cluster":"","maxage":"5000","usermenu":"show","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"deg","showgrid":"true","allowFileDrop":"true","path":"/tak-map","overlist":"DR,CO,RA,DN,BU,RW,SN,AC,TL,HM","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriO,EsriDG,NatGeo,OpTop,SW","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":1240,"y":220,"wires":[]},{"id":"71787ea1acdd3c9a","type":"worldmap in","z":"db2b165142e8c36a","name":"","path":"/tak-map","events":"connect","x":200,"y":280,"wires":[["3f1c45fae45fc2ae"]]},{"id":"361f42b16f36cb0d","type":"worldmap in","z":"db2b165142e8c36a","name":"event","path":"/tak-map","events":"other","x":190,"y":380,"wires":[["9e3dcf9240c85d5a"]]},{"id":"bf877041be384805","type":"debug","z":"db2b165142e8c36a","name":"No form!!","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":540,"wires":[]},{"id":"3f1c45fae45fc2ae","type":"function","z":"db2b165142e8c36a","name":"form","func":"msg.payload = { command: {\ncontextmenu: String.raw`\n\n\n\n\n\n\n\n\n\n\n
Attitude
Type
TimeoutS
Name
\n`\n}}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":260,"wires":[["3a1205e793d055fa"]]},{"id":"17650e362d95de70","type":"config","z":"db2b165142e8c36a","name":"FTH Global Config","properties":[{"p":"FTH_FTS_URL","pt":"global","to":"0.0.0.0","tot":"str"},{"p":"FTH_FTS_TCP_Port","pt":"global","to":"8087","tot":"str"},{"p":"FTH_FTS_API_Port","pt":"global","to":"19023","tot":"str"},{"p":"FTH_FTS_API_Auth","pt":"global","to":"token","tot":"str"},{"p":"FTH_FTS_STREAM_Port","pt":"global","to":"8554","tot":"str"},{"p":"FTH_FTS_VIDEO_URL","pt":"global","to":"0.0.0.0","tot":"str"},{"p":"FTH_FTS_VIDEO_API_PORT","pt":"global","to":"9997","tot":"str"}],"active":true,"x":190,"y":80,"wires":[]},{"id":"f9c1c3d2b0dcf4da","type":"http request","z":"db2b165142e8c36a","name":"Post COT to FTS","method":"POST","ret":"txt","paytoqs":"ignore","url":"{{{addr}}}:{{{port}}}/ManageGeoObject/postGeoObject","tls":"","persist":false,"proxy":"","authType":"bearer","senderr":false,"x":890,"y":440,"wires":[["e42b1fdd3dd68980"]]},{"id":"64334cba1273e06c","type":"function","z":"db2b165142e8c36a","name":"","func":"\n msg.addr = global.get('FTH_FTS_URL');\n msg.port = global.get('FTH_FTS_API_Port');\nlet lon = msg.payload.lon;\n let lat = msg.payload.lat;\n let attitude = msg.payload.value.attitude;\n let geoobject = msg.payload.value.geobject;\n let timeOut = msg.payload.name.timeout;\n let aName = msg.payload.value.name;\n let token = global.get('FTH_FTS_API_Auth');\n\n \nmsg.headers = {};\nmsg.headers['Authorization'] = \"Bearer \"+ token;\n \n\nmsg.payload = [];\n\n msg.payload={\n longitude: lon,\n latitude: lat,\nattitude: attitude,\ngeoObject: geoobject,\nhow: \"nonCoT\",\nname: aName,\ntimeout: timeOut \n };\n \nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":440,"wires":[["f9c1c3d2b0dcf4da"]]},{"id":"e42b1fdd3dd68980","type":"debug","z":"db2b165142e8c36a","name":"API Response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1120,"y":440,"wires":[]},{"id":"9e3dcf9240c85d5a","type":"switch","z":"db2b165142e8c36a","name":"check message","property":"payload.value.name","propertyType":"msg","rules":[{"t":"nempty"},{"t":"empty"}],"checkall":"true","repair":false,"outputs":2,"x":440,"y":420,"wires":[["64334cba1273e06c"],["bf877041be384805"]]},{"id":"647af514a3fc61f2","type":"comment","z":"db2b165142e8c36a","name":"Add the menu","info":"","x":210,"y":240,"wires":[]},{"id":"57d4f22db956d69b","type":"comment","z":"db2b165142e8c36a","name":"Get the result from the post","info":"","x":230,"y":340,"wires":[]},{"id":"0565f229206163d0","type":"comment","z":"db2b165142e8c36a","name":"CONFIG ME!!!!","info":"please set the variables to the proper IP","x":170,"y":40,"wires":[]},{"id":"4134eaa89c5c71e0","type":"debug","z":"db2b165142e8c36a","name":"Strem output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":770,"y":80,"wires":[]},{"id":"4b3df29696d9fc8f","type":"function","z":"db2b165142e8c36a","name":"CoT to Map","func":"/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\nlet ttl = 3600;\nlet deleted = false; //= 'undefined';\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\n\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; }\n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\"}${et[4] || \"-\"}${et[5] || \"-\"}--------`;\n\n/* Now handle all the weird extra CoT ones... */\nif ((/a-.-X-i-o/).test(eventType)) { SIDC = `E${affil}IP--------` }\nelse if ((/a-.-X-i-g-a/).test(eventType)) { SIDC = `EFNPAB------` }\nelse if ((/a-.-X-i-g-e/).test(eventType)) { SIDC = `EFNPAC------` }\nelse if ((/a-.-X-i-g-l/).test(eventType)) { SIDC = `EFNPAD------` }\nelse if ((/a-.-X-i-g-s/).test(eventType)) { SIDC = `EFNPAE------` }\nelse if ((/a-.-X-i-g-v-e/).test(eventType)) { SIDC = `WFSPWSVE----` }\nelse if ((/a-.-X-i-g-v/).test(eventType)) { SIDC = `EFNPAG------` }\nelse if ((/a-.-X-i-g/).test(eventType)) { SIDC = `E${affil}NPA-------` }\nelse if ((/a-.-X-i-m-c/).test(eventType)) { SIDC = `WFSPWSTSS---` }\nelse if ((/a-.-X-i-m-d/).test(eventType)) { SIDC = `EFNPBB------` }\nelse if ((/a-.-X-i-m-n/).test(eventType)) { SIDC = `EFNPBM------` }\nelse if ((/a-.-X-i-f/).test(eventType)) { SIDC = `E${affil}IPC-------` }\nelse if ((/a-.-X-i-h/).test(eventType)) { SIDC = `E${affil}OPA-------` }\nelse if ((/a-.-X-i-t-v-a/).test(eventType)) { SIDC = `E${affil}IPHA------` }\n\nelse if ((/a-.-G-I-X-H/).test(eventType)) { SIDC = `S${affil}GPIXH---H-` }\nelse if ((/a-.-G-I-i-l/).test(eventType)) { SIDC = `E${affil}OPDJC-----` }\nelse if ((/a-.-G-I-R-P/).test(eventType)) { SIDC = `S${affil}GPIRP---H-` }\nelse if ((/a-.-G-I-U-E/).test(eventType)) { SIDC = `S${affil}GPIUE---H-` }\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/a-.-G-I-i-e/).test(eventType)) { SIDC = `E${affil}OPBC------` }\n\nelse if ((/a-.-G-U-C-V-R-E/).test(eventType)) { SIDC = `S${affil}APMFO-----` }\nelse if ((/a-.-A-M-F-Q-H/).test(eventType)) { SIDC = `S${affil}APMFH-----` }\n\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/b-r-.-O-I-V/).test(eventType)) { SIDC = `S${affil}EFIPAC------` }\n// emergency open send symbol and reduce ttl\nelse if ((/b-a-o-opn/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('emergency Open');\n}\nelse if ((/b-a-o-tbl/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('trouble emergency Open');\n}\nelse if ((/b-a-o-pan/).test(eventType)) {\n SIDC = `S${affil}GXU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('ring the bell emergency Open');\n}\n//emergency is closed SFGDU-------\nelse if ((/b-a-o-can/).test(eventType)) {\n //deleted = true\n SIDC = `SFGPUCI-----`; \n node.warn('emergency closed');\n }\n\n\nconsole.log(eventType, \"--->\", SIDC);\nSIDC = SIDC.substr(0, 12).toUpperCase();\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"];\n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid) {\n bearing = course;\n }\n }\n\n let _speed = +track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = +event[\"point\"][0][\"$\"][\"ce\"];\nif (ce != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\nelse {\n weblink = 'https://spatialillusions.com/unitgenerator/';\n}\n\nlet lat = +event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = +event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = +event[\"point\"][0][\"$\"][\"hae\"];\nif (alt == invalid) { alt = undefined; }\n\nif (lat == 0 && lon == 0 && !alt) {\n console.log(callsign, \"is at null island.\", eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n deleted: deleted,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n type: eventType,\n SIDC: SIDC,\n ttl: ttl,\n weblink: weblink,\n layer: eventType,\n options: { scale: 0.8 }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":160,"wires":[["3a1205e793d055fa","885b808dd8a4895d"]]},{"id":"bbaeafe55e6385e4","type":"comment","z":"db2b165142e8c36a","name":"APi Token here","info":"","x":880,"y":400,"wires":[]}] \ No newline at end of file +[ + { + "id": "db2b165142e8c36a", + "type": "tab", + "label": "web Map 2", + "disabled": false, + "info": "Gets the flow of COT from FTS and displays on a web map (credit @ampledata#8354).\n The FTH WebMap integration can be installed as part of FTS UI or a stand alone, e.g. for a dashboard. ", + "env": [] + }, + { + "id": "9946c2419fd3c3ae", + "type": "inject", + "z": "db2b165142e8c36a", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "120", + "crontab": "", + "once": true, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 160, + "wires": [ + [ + "33593e8ece650844" + ] + ] + }, + { + "id": "33593e8ece650844", + "type": "function", + "z": "db2b165142e8c36a", + "name": "CoT Ping", + "func": "msg.host = env.get(\"FTH_FTS_URL\") || global.get('FTH_FTS_URL');\nmsg.port = env.get(\"FTH_FTS_TCP_Port\") || global.get('FTH_FTS_TCP_Port');\nconst dt = Date.now();\nconst dtD = new Date(dt).toISOString();\nconst dtD5 = new Date(dt + 250000).toISOString();\n\nmsg.payload = {\n event: {\n \"$\": { \n version: \"2.0\", \n type: \"t-x-d-d\", \n uid : \"node-red\",\n time: dtD,\n start: dtD,\n stale: dtD5,\n how: \"m-g\"\n }\n }\n \n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 320, + "y": 160, + "wires": [ + [ + "7a0f7d3e4099e946" + ] + ] + }, + { + "id": "7a0f7d3e4099e946", + "type": "xml", + "z": "db2b165142e8c36a", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 450, + "y": 160, + "wires": [ + [ + "52446a6c25605721" + ] + ] + }, + { + "id": "7c52084b496b83bc", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "Keep Server Connection Alive", + "info": "", + "x": 200, + "y": 120, + "wires": [] + }, + { + "id": "52446a6c25605721", + "type": "tcp request", + "z": "db2b165142e8c36a", + "name": "FTS Server", + "server": "", + "port": "", + "out": "sit", + "ret": "buffer", + "splitc": " ", + "newline": "", + "trim": false, + "tls": "", + "x": 590, + "y": 160, + "wires": [ + [ + "3c62054705949d69", + "4134eaa89c5c71e0" + ] + ] + }, + { + "id": "3c62054705949d69", + "type": "function", + "z": "db2b165142e8c36a", + "name": "buf->str", + "func": "/*\nlet oldPayload = msg.payload;\nmsg.payload = oldPayload.toString();\nreturn msg;\n*/\n\nvar rest = context.rest || \"\";\nvar p = rest + msg.payload.toString();\nvar b = p.split(\"\");\nwhile (b.length > 1) {\n var m = b.shift();\n node.send({ payload: m + \"\" });\n}\ncontext.rest = b;\nreturn;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 740, + "y": 160, + "wires": [ + [ + "91213e2448bab185" + ] + ] + }, + { + "id": "91213e2448bab185", + "type": "xml", + "z": "db2b165142e8c36a", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 870, + "y": 160, + "wires": [ + [ + "4b3df29696d9fc8f" + ] + ] + }, + { + "id": "05f4656c40958267", + "type": "function", + "z": "db2b165142e8c36a", + "name": "CoT to Map", + "func": "/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\n\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\nconsole.log(eventType);\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; } \n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\" }${et[4] || \"-\" }${et[5] || \"-\" }--------`;\nSIDC = SIDC.substr(0,12).toUpperCase();\n\n/* Now handle all the weird extra CoT ones... */\nif (eventType === \"a-h-X-i-o\") { \n console.log(\"XIO--->\",SIDC)\n SIDC = \"EHIP--------\" }\nelse if (eventType === \"a-h-X-i-m-d\") { \n console.log(\"XIMD--->\",SIDC)\n SIDC = \"EHNPBB------\" }\nelse if (eventType === \"a-h-X-i-g-e\") { \n console.log(\"XIGE--->\",SIDC)\n SIDC = \"EHNPAC------\" }\n\n\nconsole.log(SIDC);\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"]; \n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid && course.toString() !== \"0\") {\n bearing = course;\n }\n }\n\n let _speed = track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed.toString() !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = event[\"point\"][0][\"$\"][\"ce\"];\nif (ce.toString() != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\n\nlet lat = event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = event[\"point\"][0][\"$\"][\"hae\"];\n\nif (lat == 0 && lon == 0 && alt == 0) {\n console.log(callsign,\"is at null island.\",eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n SIDC: SIDC,\n ttl: 600,\n weblink: weblink,\n layer: eventType,\n options: {scale:0.8}\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "8c35a77a0d0d3f3d", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "Connect to FTS", + "info": "", + "x": 600, + "y": 120, + "wires": [] + }, + { + "id": "586a2364ecb46529", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "Convert to Worldmap Payload", + "info": "", + "x": 1060, + "y": 120, + "wires": [] + }, + { + "id": "885b808dd8a4895d", + "type": "debug", + "z": "db2b165142e8c36a", + "name": "TAK map output", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1260, + "y": 80, + "wires": [] + }, + { + "id": "3a1205e793d055fa", + "type": "worldmap", + "z": "db2b165142e8c36a", + "name": "TAK Map", + "lat": "43", + "lon": "13", + "zoom": "4", + "layer": "EsriDG", + "cluster": "", + "maxage": "5000", + "usermenu": "show", + "layers": "show", + "panit": "false", + "panlock": "false", + "zoomlock": "false", + "hiderightclick": "false", + "coords": "deg", + "showgrid": "true", + "allowFileDrop": "true", + "path": "/tak-map", + "overlist": "DR,CO,RA,DN,BU,RW,SN,AC,TL,HM", + "maplist": "OSMG,OSMC,EsriC,EsriS,EsriT,EsriO,EsriDG,NatGeo,OpTop,SW", + "mapname": "", + "mapurl": "", + "mapopt": "", + "mapwms": false, + "x": 1240, + "y": 220, + "wires": [] + }, + { + "id": "71787ea1acdd3c9a", + "type": "worldmap in", + "z": "db2b165142e8c36a", + "name": "", + "path": "/tak-map", + "events": "connect", + "x": 200, + "y": 280, + "wires": [ + [ + "3f1c45fae45fc2ae" + ] + ] + }, + { + "id": "361f42b16f36cb0d", + "type": "worldmap in", + "z": "db2b165142e8c36a", + "name": "event", + "path": "/tak-map", + "events": "other", + "x": 190, + "y": 380, + "wires": [ + [ + "9e3dcf9240c85d5a" + ] + ] + }, + { + "id": "bf877041be384805", + "type": "debug", + "z": "db2b165142e8c36a", + "name": "No form!!", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 560, + "y": 540, + "wires": [] + }, + { + "id": "3f1c45fae45fc2ae", + "type": "function", + "z": "db2b165142e8c36a", + "name": "form", + "func": "msg.payload = { command: {\ncontextmenu: String.raw`\n\n\n\n\n\n\n\n\n\n\n
Attitude
Type
TimeoutS
Name
\n`\n}}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1010, + "y": 260, + "wires": [ + [ + "3a1205e793d055fa" + ] + ] + }, + { + "id": "17650e362d95de70", + "type": "config", + "z": "db2b165142e8c36a", + "name": "FTH Global Config", + "properties": [ + { + "p": "FTH_FTS_URL", + "pt": "global", + "to": "0.0.0.0", + "tot": "str" + }, + { + "p": "FTH_FTS_TCP_Port", + "pt": "global", + "to": "8087", + "tot": "str" + }, + { + "p": "FTH_FTS_API_Port", + "pt": "global", + "to": "19023", + "tot": "str" + }, + { + "p": "FTH_FTS_API_Auth", + "pt": "global", + "to": "token", + "tot": "str" + }, + { + "p": "FTH_FTS_STREAM_Port", + "pt": "global", + "to": "8554", + "tot": "str" + }, + { + "p": "FTH_FTS_VIDEO_URL", + "pt": "global", + "to": "0.0.0.0", + "tot": "str" + }, + { + "p": "FTH_FTS_VIDEO_API_PORT", + "pt": "global", + "to": "9997", + "tot": "str" + } + ], + "active": true, + "x": 190, + "y": 80, + "wires": [] + }, + { + "id": "f9c1c3d2b0dcf4da", + "type": "http request", + "z": "db2b165142e8c36a", + "name": "Post COT to FTS", + "method": "POST", + "ret": "txt", + "paytoqs": "ignore", + "url": "{{{addr}}}:{{{port}}}/ManageGeoObject/postGeoObject", + "tls": "", + "persist": false, + "proxy": "", + "authType": "bearer", + "senderr": false, + "x": 890, + "y": 440, + "wires": [ + [ + "e42b1fdd3dd68980" + ] + ] + }, + { + "id": "64334cba1273e06c", + "type": "function", + "z": "db2b165142e8c36a", + "name": "", + "func": "\n msg.addr = env.get(\"FTH_FTS_URL\") || global.get('FTH_FTS_URL');\n msg.port = env.get(\"FTH_FTS_API_Port\") || global.get('FTH_FTS_API_Port');\nlet lon = msg.payload.lon;\n let lat = msg.payload.lat;\n let attitude = msg.payload.value.attitude;\n let geoobject = msg.payload.value.geobject;\n let timeOut = msg.payload.name.timeout;\n let aName = msg.payload.value.name;\nlet token = env.get(\"FTH_FTS_API_Auth\") || global.get('FTH_FTS_API_Auth');\n\n \nmsg.headers = {};\nmsg.headers['Authorization'] = \"Bearer \"+ token;\n \n\nmsg.payload = [];\n\n msg.payload={\n longitude: lon,\n latitude: lat,\nattitude: attitude,\ngeoObject: geoobject,\nhow: \"nonCoT\",\nname: aName,\ntimeout: timeOut \n };\n \nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 680, + "y": 440, + "wires": [ + [ + "f9c1c3d2b0dcf4da" + ] + ] + }, + { + "id": "e42b1fdd3dd68980", + "type": "debug", + "z": "db2b165142e8c36a", + "name": "API Response", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1120, + "y": 440, + "wires": [] + }, + { + "id": "9e3dcf9240c85d5a", + "type": "switch", + "z": "db2b165142e8c36a", + "name": "check message", + "property": "payload.value.name", + "propertyType": "msg", + "rules": [ + { + "t": "nempty" + }, + { + "t": "empty" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 440, + "y": 420, + "wires": [ + [ + "64334cba1273e06c" + ], + [ + "bf877041be384805" + ] + ] + }, + { + "id": "647af514a3fc61f2", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "Add the menu", + "info": "", + "x": 210, + "y": 240, + "wires": [] + }, + { + "id": "57d4f22db956d69b", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "Get the result from the post", + "info": "", + "x": 230, + "y": 340, + "wires": [] + }, + { + "id": "0565f229206163d0", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "CONFIG ME!!!!", + "info": "please set the variables to the proper IP", + "x": 170, + "y": 40, + "wires": [] + }, + { + "id": "4134eaa89c5c71e0", + "type": "debug", + "z": "db2b165142e8c36a", + "name": "Strem output", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 770, + "y": 80, + "wires": [] + }, + { + "id": "4b3df29696d9fc8f", + "type": "function", + "z": "db2b165142e8c36a", + "name": "CoT to Map", + "func": "/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\nlet ttl = 3600;\nlet deleted = false; //= 'undefined';\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\n\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; }\n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\"}${et[4] || \"-\"}${et[5] || \"-\"}--------`;\n\n/* Now handle all the weird extra CoT ones... */\nif ((/a-.-X-i-o/).test(eventType)) { SIDC = `E${affil}IP--------` }\nelse if ((/a-.-X-i-g-a/).test(eventType)) { SIDC = `EFNPAB------` }\nelse if ((/a-.-X-i-g-e/).test(eventType)) { SIDC = `EFNPAC------` }\nelse if ((/a-.-X-i-g-l/).test(eventType)) { SIDC = `EFNPAD------` }\nelse if ((/a-.-X-i-g-s/).test(eventType)) { SIDC = `EFNPAE------` }\nelse if ((/a-.-X-i-g-v-e/).test(eventType)) { SIDC = `WFSPWSVE----` }\nelse if ((/a-.-X-i-g-v/).test(eventType)) { SIDC = `EFNPAG------` }\nelse if ((/a-.-X-i-g/).test(eventType)) { SIDC = `E${affil}NPA-------` }\nelse if ((/a-.-X-i-m-c/).test(eventType)) { SIDC = `WFSPWSTSS---` }\nelse if ((/a-.-X-i-m-d/).test(eventType)) { SIDC = `EFNPBB------` }\nelse if ((/a-.-X-i-m-n/).test(eventType)) { SIDC = `EFNPBM------` }\nelse if ((/a-.-X-i-f/).test(eventType)) { SIDC = `E${affil}IPC-------` }\nelse if ((/a-.-X-i-h/).test(eventType)) { SIDC = `E${affil}OPA-------` }\nelse if ((/a-.-X-i-t-v-a/).test(eventType)) { SIDC = `E${affil}IPHA------` }\n\nelse if ((/a-.-G-I-X-H/).test(eventType)) { SIDC = `S${affil}GPIXH---H-` }\nelse if ((/a-.-G-I-i-l/).test(eventType)) { SIDC = `E${affil}OPDJC-----` }\nelse if ((/a-.-G-I-R-P/).test(eventType)) { SIDC = `S${affil}GPIRP---H-` }\nelse if ((/a-.-G-I-U-E/).test(eventType)) { SIDC = `S${affil}GPIUE---H-` }\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/a-.-G-I-i-e/).test(eventType)) { SIDC = `E${affil}OPBC------` }\n\nelse if ((/a-.-G-U-C-V-R-E/).test(eventType)) { SIDC = `S${affil}APMFO-----` }\nelse if ((/a-.-A-M-F-Q-H/).test(eventType)) { SIDC = `S${affil}APMFH-----` }\n\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/b-r-.-O-I-V/).test(eventType)) { SIDC = `S${affil}EFIPAC------` }\n// emergency open send symbol and reduce ttl\nelse if ((/b-a-o-opn/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('emergency Open');\n}\nelse if ((/b-a-o-tbl/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('trouble emergency Open');\n}\nelse if ((/b-a-o-pan/).test(eventType)) {\n SIDC = `S${affil}GXU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('ring the bell emergency Open');\n}\n//emergency is closed SFGDU-------\nelse if ((/b-a-o-can/).test(eventType)) {\n //deleted = true\n SIDC = `SFGPUCI-----`; \n node.warn('emergency closed');\n }\n\n\nconsole.log(eventType, \"--->\", SIDC);\nSIDC = SIDC.substr(0, 12).toUpperCase();\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"];\n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid) {\n bearing = course;\n }\n }\n\n let _speed = +track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = +event[\"point\"][0][\"$\"][\"ce\"];\nif (ce != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\nelse {\n weblink = 'https://spatialillusions.com/unitgenerator/';\n}\n\nlet lat = +event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = +event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = +event[\"point\"][0][\"$\"][\"hae\"];\nif (alt == invalid) { alt = undefined; }\n\nif (lat == 0 && lon == 0 && !alt) {\n console.log(callsign, \"is at null island.\", eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n deleted: deleted,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n type: eventType,\n SIDC: SIDC,\n ttl: ttl,\n weblink: weblink,\n layer: eventType,\n options: { scale: 0.8 }\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1010, + "y": 160, + "wires": [ + [ + "3a1205e793d055fa", + "885b808dd8a4895d" + ] + ] + }, + { + "id": "bbaeafe55e6385e4", + "type": "comment", + "z": "db2b165142e8c36a", + "name": "APi Token here", + "info": "", + "x": 880, + "y": 400, + "wires": [] + } +] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b5e3e0b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + FreeTAKHub-webmap: + image: freetak-webmap:latest + pull_policy: build + build: + context: . + dockerfile: Dockerfile + ports: + - '1880:1880' + environment: + FTH_FTS_URL: 'free-tak-server' + FTH_FTS_TCP_Port: 8087 + FTH_FTS_API_Auth: ''