Added navigation object
Former-commit-id: c6fef444bc1d4fa3389206ac3609fad1f2a41b9c
This commit is contained in:
parent
068e19db22
commit
fce0a0e939
12 changed files with 194 additions and 39 deletions
|
|
@ -7,10 +7,7 @@ import se.hal.deamon.PCDataSynchronizationClient;
|
||||||
import se.hal.deamon.PCDataSynchronizationDaemon;
|
import se.hal.deamon.PCDataSynchronizationDaemon;
|
||||||
import se.hal.intf.HalDaemon;
|
import se.hal.intf.HalDaemon;
|
||||||
import se.hal.intf.HalHttpPage;
|
import se.hal.intf.HalHttpPage;
|
||||||
import se.hal.page.SensorConfigHttpPage;
|
import se.hal.page.*;
|
||||||
import se.hal.page.PCHeatMapHttpPage;
|
|
||||||
import se.hal.page.PCOverviewHttpPage;
|
|
||||||
import se.hal.page.UserConfigHttpPage;
|
|
||||||
import se.hal.struct.Event;
|
import se.hal.struct.Event;
|
||||||
import se.hal.struct.Sensor;
|
import se.hal.struct.Sensor;
|
||||||
import zutil.db.DBConnection;
|
import zutil.db.DBConnection;
|
||||||
|
|
@ -72,17 +69,20 @@ public class HalServer {
|
||||||
|
|
||||||
|
|
||||||
// init http server
|
// init http server
|
||||||
|
HalHttpPage.getRootNav().addSubNav(new HalNavigation("sensors", "Sensors"));
|
||||||
|
HalHttpPage.getRootNav().addSubNav(new HalNavigation("events", "Events"));
|
||||||
pages = new HalHttpPage[]{
|
pages = new HalHttpPage[]{
|
||||||
new PCOverviewHttpPage(),
|
new PCOverviewHttpPage(),
|
||||||
new PCHeatMapHttpPage(),
|
new PCHeatMapHttpPage(),
|
||||||
new SensorConfigHttpPage(),
|
new SensorConfigHttpPage(),
|
||||||
|
new EventConfigHttpPage(),
|
||||||
new UserConfigHttpPage(),
|
new UserConfigHttpPage(),
|
||||||
};
|
};
|
||||||
HttpServer http = new HttpServer(HalContext.getIntegerProperty("http_port"));
|
HttpServer http = new HttpServer(HalContext.getIntegerProperty("http_port"));
|
||||||
http.setDefaultPage(new HttpFilePage(FileUtil.find("web-resource/")));
|
http.setDefaultPage(new HttpFilePage(FileUtil.find("web-resource/")));
|
||||||
http.setPage("/", pages[0]);
|
http.setPage("/", pages[0]);
|
||||||
for(HalHttpPage page : pages){
|
for(HalHttpPage page : pages){
|
||||||
http.setPage(page.getURL(), page);
|
http.setPage(page.getId(), page);
|
||||||
}
|
}
|
||||||
http.start();
|
http.start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package se.hal.intf;
|
package se.hal.intf;
|
||||||
|
|
||||||
import se.hal.HalContext;
|
import se.hal.HalContext;
|
||||||
|
import se.hal.page.HalNavigation;
|
||||||
import se.hal.struct.User;
|
import se.hal.struct.User;
|
||||||
import zutil.db.DBConnection;
|
import zutil.db.DBConnection;
|
||||||
import zutil.io.file.FileUtil;
|
import zutil.io.file.FileUtil;
|
||||||
|
|
@ -17,26 +18,25 @@ import java.util.Map;
|
||||||
* Created by Ziver on 2015-12-10.
|
* Created by Ziver on 2015-12-10.
|
||||||
*/
|
*/
|
||||||
public abstract class HalHttpPage implements HttpPage{
|
public abstract class HalHttpPage implements HttpPage{
|
||||||
|
private static final String TEMPLATE = "web-resource/index.tmpl";
|
||||||
|
private static HalNavigation rootNav = new HalNavigation();
|
||||||
|
private static HalNavigation userNav = new HalNavigation();
|
||||||
|
|
||||||
private static ArrayList<HalHttpPage> pages = new ArrayList<>();
|
private HalNavigation nav;
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
public HalHttpPage(String name, String id){
|
public HalHttpPage(String name, String id){
|
||||||
this.name = name;
|
this.nav = new HalNavigation(id, name);
|
||||||
this.id = id;
|
|
||||||
pages.add(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName(){
|
public String getName(){
|
||||||
return name;
|
return nav.getName();
|
||||||
}
|
}
|
||||||
public String getId(){
|
public String getId(){
|
||||||
return id;
|
return nav.getId();
|
||||||
}
|
}
|
||||||
public String getURL(){
|
public HalNavigation getNav(){
|
||||||
return "/" + this.id;
|
return nav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,9 +48,11 @@ public abstract class HalHttpPage implements HttpPage{
|
||||||
try {
|
try {
|
||||||
DBConnection db = HalContext.getDB();
|
DBConnection db = HalContext.getDB();
|
||||||
|
|
||||||
Templator tmpl = new Templator(FileUtil.find("web-resource/index.tmpl"));
|
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
|
||||||
tmpl.set("user", User.getLocalUser(db));
|
tmpl.set("user", User.getLocalUser(db));
|
||||||
tmpl.set("navigation", pages);
|
tmpl.set("nav", nav.getNavBreadcrumb().get(1));
|
||||||
|
tmpl.set("rootNav", rootNav);
|
||||||
|
tmpl.set("userNav", userNav);
|
||||||
tmpl.set("content", httpRespond(session, cookie, request));
|
tmpl.set("content", httpRespond(session, cookie, request));
|
||||||
out.print(tmpl.compile());
|
out.print(tmpl.compile());
|
||||||
|
|
||||||
|
|
@ -60,6 +62,14 @@ public abstract class HalHttpPage implements HttpPage{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static HalNavigation getRootNav(){
|
||||||
|
return rootNav;
|
||||||
|
}
|
||||||
|
public static HalNavigation getUserNav(){
|
||||||
|
return userNav;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract Templator httpRespond(
|
public abstract Templator httpRespond(
|
||||||
Map<String, Object> session,
|
Map<String, Object> session,
|
||||||
Map<String, String> cookie,
|
Map<String, String> cookie,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import se.hal.ControllerManager;
|
||||||
import se.hal.HalContext;
|
import se.hal.HalContext;
|
||||||
import se.hal.intf.HalHttpPage;
|
import se.hal.intf.HalHttpPage;
|
||||||
import se.hal.struct.Event;
|
import se.hal.struct.Event;
|
||||||
import se.hal.struct.Sensor;
|
|
||||||
import se.hal.struct.User;
|
import se.hal.struct.User;
|
||||||
import zutil.db.DBConnection;
|
import zutil.db.DBConnection;
|
||||||
import zutil.io.file.FileUtil;
|
import zutil.io.file.FileUtil;
|
||||||
|
|
@ -26,9 +25,10 @@ public class EventConfigHttpPage extends HalHttpPage {
|
||||||
|
|
||||||
public EventConfigHttpPage() {
|
public EventConfigHttpPage() {
|
||||||
super("Configuration", "event_config");
|
super("Configuration", "event_config");
|
||||||
|
super.getRootNav().getSubNav("events").addSubNav(super.getNav());
|
||||||
|
|
||||||
eventConfigurations = new EventDataParams[
|
eventConfigurations = new EventDataParams[
|
||||||
ControllerManager.getInstance().getAvailableSensors().size()];
|
ControllerManager.getInstance().getAvailableEvents().size()];
|
||||||
int i=0;
|
int i=0;
|
||||||
for(Class c : ControllerManager.getInstance().getAvailableEvents()){
|
for(Class c : ControllerManager.getInstance().getAvailableEvents()){
|
||||||
eventConfigurations[i] = new EventDataParams();
|
eventConfigurations[i] = new EventDataParams();
|
||||||
|
|
@ -53,12 +53,12 @@ public class EventConfigHttpPage extends HalHttpPage {
|
||||||
int id = (request.containsKey("id") ? Integer.parseInt(request.get("id")) : -1);
|
int id = (request.containsKey("id") ? Integer.parseInt(request.get("id")) : -1);
|
||||||
Event event;
|
Event event;
|
||||||
switch(request.get("action")) {
|
switch(request.get("action")) {
|
||||||
// Local Sensors
|
// Local events
|
||||||
case "create_local_event":
|
case "create_local_event":
|
||||||
event = new Event();
|
event = new Event();
|
||||||
event.setName(request.get("name"));
|
event.setName(request.get("name"));
|
||||||
event.setType(request.get("type"));
|
event.setType(request.get("type"));
|
||||||
//sensor.setConfig(request.get("config"));
|
//event.setConfig(request.get("config"));
|
||||||
event.setUser(localUser);
|
event.setUser(localUser);
|
||||||
event.save(db);
|
event.save(db);
|
||||||
case "modify_local_event":
|
case "modify_local_event":
|
||||||
|
|
@ -66,7 +66,7 @@ public class EventConfigHttpPage extends HalHttpPage {
|
||||||
if(event != null){
|
if(event != null){
|
||||||
event.setName(request.get("name"));
|
event.setName(request.get("name"));
|
||||||
event.setType(request.get("type"));
|
event.setType(request.get("type"));
|
||||||
//sensor.setConfig(request.get("config"));
|
//event.setConfig(request.get("config"));
|
||||||
event.setUser(localUser);
|
event.setUser(localUser);
|
||||||
event.save(db);
|
event.save(db);
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ public class EventConfigHttpPage extends HalHttpPage {
|
||||||
tmpl.set("localEventConf", eventConfigurations);
|
tmpl.set("localEventConf", eventConfigurations);
|
||||||
|
|
||||||
|
|
||||||
tmpl.set("availableSensors", ControllerManager.getInstance().getAvailableSensors());
|
tmpl.set("availableEvents", ControllerManager.getInstance().getAvailableEvents());
|
||||||
|
|
||||||
return tmpl;
|
return tmpl;
|
||||||
|
|
||||||
|
|
|
||||||
138
src/se/hal/page/HalNavigation.java
Executable file
138
src/se/hal/page/HalNavigation.java
Executable file
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Ziver
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package se.hal.page;
|
||||||
|
|
||||||
|
import se.hal.intf.HalHttpPage;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Ziver on 2015-04-02.
|
||||||
|
*/
|
||||||
|
public class HalNavigation implements Iterable{
|
||||||
|
private static HashMap<String, HalNavigation> navMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private String name;
|
||||||
|
private HalNavigation parentNav;
|
||||||
|
private ArrayList<HalNavigation> subNav;
|
||||||
|
private HalHttpPage resource;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a root navigation object
|
||||||
|
*/
|
||||||
|
public HalNavigation() {
|
||||||
|
this(null, null);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a sub navigation object with no resource
|
||||||
|
*/
|
||||||
|
public HalNavigation(String id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.subNav = new ArrayList<>();
|
||||||
|
navMap.put(id, this);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a sub navigation object
|
||||||
|
*/
|
||||||
|
/* public HalNavigation(HalHttpPage page) {
|
||||||
|
this.id = page.getId();
|
||||||
|
this.name = page.getName();
|
||||||
|
this.subNav = new ArrayList<>();
|
||||||
|
this.resource = page;
|
||||||
|
navMap.put(id, this);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator iterator() {
|
||||||
|
return subNav.iterator();
|
||||||
|
}
|
||||||
|
public List<HalNavigation> getSubNavs() {
|
||||||
|
return subNav;
|
||||||
|
}
|
||||||
|
public HalNavigation getSubNav(String id) {
|
||||||
|
for(HalNavigation nav : subNav) {
|
||||||
|
if(nav.equals(id))
|
||||||
|
return nav;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalNavigation addSubNav(HalNavigation nav) {
|
||||||
|
nav.setParentNav(this);
|
||||||
|
subNav.add(nav);
|
||||||
|
return nav;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setParentNav(HalNavigation nav){
|
||||||
|
this.parentNav = nav;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o){
|
||||||
|
if(o instanceof String)
|
||||||
|
return this.id.equals(o);
|
||||||
|
return this == o ||
|
||||||
|
(o != null && this.id.equals(((HalNavigation)o).id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HalNavigation> getNavBreadcrumb() {
|
||||||
|
LinkedList list = new LinkedList();
|
||||||
|
|
||||||
|
HalNavigation current = this;
|
||||||
|
while(current != null && id != null){
|
||||||
|
list.addFirst(current);
|
||||||
|
current = current.parentNav;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getURL(){
|
||||||
|
return "/" + this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getId(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
public String getName(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public void setName(String name){
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
public HalNavigation getParent(){
|
||||||
|
return parentNav;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static HalNavigation getNav(String id){
|
||||||
|
return navMap.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ public class PCHeatMapHttpPage extends HalHttpPage {
|
||||||
|
|
||||||
public PCHeatMapHttpPage() {
|
public PCHeatMapHttpPage() {
|
||||||
super("Heatmap", "pc_heatmap");
|
super("Heatmap", "pc_heatmap");
|
||||||
|
super.getRootNav().getSubNav("sensors").addSubNav(super.getNav());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public class PCOverviewHttpPage extends HalHttpPage {
|
||||||
|
|
||||||
public PCOverviewHttpPage() {
|
public PCOverviewHttpPage() {
|
||||||
super("Power;Challenge", "pc_overview");
|
super("Power;Challenge", "pc_overview");
|
||||||
|
super.getRootNav().getSubNav("sensors").addSubNav(super.getNav());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ public class SensorConfigHttpPage extends HalHttpPage {
|
||||||
|
|
||||||
public SensorConfigHttpPage() {
|
public SensorConfigHttpPage() {
|
||||||
super("Configuration", "sensor_config");
|
super("Configuration", "sensor_config");
|
||||||
|
super.getRootNav().getSubNav("sensors").addSubNav(super.getNav());
|
||||||
|
|
||||||
sensorConfigurations = new SensorDataParams[
|
sensorConfigurations = new SensorDataParams[
|
||||||
ControllerManager.getInstance().getAvailableSensors().size()];
|
ControllerManager.getInstance().getAvailableSensors().size()];
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ public class UserConfigHttpPage extends HalHttpPage {
|
||||||
|
|
||||||
public UserConfigHttpPage() {
|
public UserConfigHttpPage() {
|
||||||
super("Profile", "user_profile");
|
super("Profile", "user_profile");
|
||||||
|
super.getUserNav().addSubNav(super.getNav());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"version": 1.0,
|
"version": 1.0,
|
||||||
"name": "Tellstick",
|
"name": "Tellstick",
|
||||||
"interfaces": [
|
"interfaces": [
|
||||||
{"se.koc.hal.intf.HalSensor": "se.koc.hal.plugin.tellstick.protocols.Oregon0x1A2D"},
|
{"se.hal.intf.HalSensor": "se.hal.plugin.tellstick.protocols.Oregon0x1A2D"},
|
||||||
{"se.koc.hal.intf.HalEvent": "se.koc.hal.plugin.tellstick.protocols.NexaSelfLearning"}
|
{"se.hal.intf.HalEvent": "se.hal.plugin.tellstick.protocols.NexaSelfLearning"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<button type="button" class="close" data-dismiss="modal">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title" id="exampleModalLabel">External User</h4>
|
<h4 class="modal-title" id="exampleModalLabel">Event</h4>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
@ -141,10 +141,6 @@
|
||||||
{{/availableEvents}}
|
{{/availableEvents}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label">Public:</label>
|
|
||||||
<input type="checkbox" class="form-control" name="sync" value="true">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id="event-data-conf">
|
<div id="event-data-conf">
|
||||||
|
|
|
||||||
|
|
@ -26,21 +26,28 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="#">HAL</a>
|
<a class="navbar-brand" href="/">HAL</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
|
{{#rootNav}}
|
||||||
<ul class="nav navbar-nav navbar-left">
|
<ul class="nav navbar-nav navbar-left">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Sensors</a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{.getName()}}</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{#navigation}}<li><a href="{{.getURL()}}">{{.getName()}}</a></li>
|
{{#.getSubNavs()}}<li><a href="{{.getURL()}}">{{.getName()}}</a></li>
|
||||||
{{/navigation}}
|
{{/.getSubNavs()}}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{{/rootNav}}
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li>
|
<li class="dropdown">
|
||||||
<a href="#">{{user.username}} <span class="glyphicon glyphicon-user"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{user.username}} <span class="glyphicon glyphicon-user"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{{#userNav}}<li><a href="{{.getURL()}}">{{.getName()}}</a></li>
|
||||||
|
{{/userNav}}
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -52,8 +59,8 @@
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
<div class="col-sm-3 col-md-2 sidebar">
|
||||||
<ul id="sub-navbar" class="nav nav-sidebar">
|
<ul id="sub-navbar" class="nav nav-sidebar">
|
||||||
<!-- <li class="active"><a href="/">Overview</a></li> -->
|
<!-- <li class="active"><a href="/">Overview</a></li> -->
|
||||||
{{#navigation}}<li><a href="{{.getURL()}}">{{.getName()}}</a></li>
|
{{#nav}}<li><a href="{{.getURL()}}">{{.getName()}}</a></li>
|
||||||
{{/navigation}}
|
{{/nav}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<button type="button" class="close" data-dismiss="modal">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title" id="exampleModalLabel">External User</h4>
|
<h4 class="modal-title" id="exampleModalLabel">Sensor</h4>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue