Add lal Web UI using http api

pull/248/head
Jae-Sung Lee 2 years ago
parent 6dff63cbfd
commit 61a7c2e128

@ -18,6 +18,7 @@ GitCommitLog=${GitCommitLog//\'/\"}
GitStatus=`git status -s`
BuildTime=`date +'%Y.%m.%d.%H%M%S'`
BuildGoVersion=`go version`
WebUITpl=`cat lal.html`
LDFlags=" \
-X 'github.com/q191201771/naza/pkg/bininfo.GitTag=${GitTag}' \
@ -25,6 +26,7 @@ LDFlags=" \
-X 'github.com/q191201771/naza/pkg/bininfo.GitStatus=${GitStatus}' \
-X 'github.com/q191201771/naza/pkg/bininfo.BuildTime=${BuildTime}' \
-X 'github.com/q191201771/naza/pkg/bininfo.BuildGoVersion=${BuildGoVersion}' \
-X 'github.com/q191201771/lal/pkg/logic.webUITpl=${WebUITpl}' \
"
echo "build" ${ROOT_DIR}/app/lalserver "..."

@ -0,0 +1,565 @@
<html>
<head>
<title>lal</title>
<style>
body {
background-color: #5e5e5e;
color: #dadada;
text-align: center;
}
table {
margin: auto;
width: 80%;
background-color: #25252b;
border-radius: 5px;
}
th {
color: #fff;
line-height: 1.4;
background-color: #19191d;
}
tbody>tr:nth-child(even) {
background-color: #29292e;
}
tbody>tr:hover {
background-color: #442727;
}
td {
color: #ccc;
line-height: 1.4;
}
td:hover {
background-color: #813737;
color: #fff;
font-weight: bold;
}
a {
color: #dadada;
}
</style>
</head>
<body>
<section>
<h1>Server Info</h1>
<table>
<tr>
<th>Server ID</th>
<td>{{.ServerID}}</td>
</tr>
<tr>
<th>Git Tag</th>
<td>{{.GitTag}}</td>
</tr>
<tr>
<th>Git Commit Log</th>
<td>{{.GitCommitLog}}</td>
</tr>
<tr>
<th>Git Status</th>
<td>{{.GitStatus}}</td>
</tr>
<tr>
<th>Build Time</th>
<td>{{.BuildTime}}</td>
</tr>
<tr>
<th>Go Version</th>
<td>{{.GoVersion}}</td>
</tr>
<tr>
<th>Runtime</th>
<td>{{.runtime}}</td>
</tr>
<tr>
<th>lal Version</th>
<td>{{.LalVersion}}</td>
</tr>
<tr>
<th>API Version</th>
<td>{{.ApiVersion}}</td>
</tr>
<tr>
<th>Notify Version</th>
<td>{{.NotifyVersion}}</td>
</tr>
<tr>
<th>Start Time</th>
<td>{{.StartTime}}</td>
</tr>
</table>
</section>
<section id="allGroupSection">
<h1>All Group</h1>
<div>
Sort:
<select id="selAllGroupSortCol">
<option value="name">Name</option>
<option value="start">Pub StartTime</option>
</select>
<input type="radio" name="rbAllGroupSort" value="asc" checked> ASC
<input type="radio" name="rbAllGroupSort" value="desc"> DESC
</div>
<div>
<input type="checkbox" id="chkAllGroupAuto" onclick="onCheckBoxAllGroupAutoClick();"> Auto
<input type="number" id="txtAllGroupInterval" value="1" min="1" onchange="onChangeTxtAllGroupInterval();">s
<button id="btnAllGroupRefresh" onclick="requestAllGroup();">Refresh</button>
</div>
<div>
Update Time: <label id="lblAllGroupUpdateTime"></label>
</div><br>
<table>
<thead>
<tr>
<th rowspan="3">#</th>
<th colspan="2">Name</th>
<th colspan="3">Video</th>
<th>Audio</th>
<th colspan="7">Pub</th>
<th rowspan="3">Subs</th>
</tr>
<tr>
<th rowspan="2">App</th>
<th rowspan="2">Stream</th>
<th rowspan="2">Codec</th>
<th rowspan="2">Width</th>
<th rowspan="2">Height</th>
<th rowspan="2">Codec</th>
<th rowspan="2">Session ID</th>
<th rowspan="2">Remote Addr</th>
<th rowspan="2">Start Time</th>
<th colspan="2">Read</th>
<th colspan="2">Write</th>
</tr>
<tr>
<th>Bytes Sum</th>
<th>Bitrate</th>
<th>Bytes Sum</th>
<th>Bitrate</th>
</tr>
</thead>
<tbody id="allGroupTable"></tbody>
</table>
</section>
<section id="groupSection" style="display: none;">
<h1 id="groupName">Group</h1>
<a href="javascript:GroupClose()">Back</a>
<div>
<input type="checkbox" id="chkGroupAuto" onclick="onCheckBoxGroupAutoClick();"> Auto
<input type="number" id="txtGroupInterval" value="1" min="1" onchange="onChangeTxtGroupInterval();">s
<button id="btnGroupRefresh" onclick="requestGroup();">Refresh</button>
</div>
<div>
Update Time: <label id="lblGroupUpdateTime"></label>
</div>
<h4>Group Info</h4>
<table>
<thead>
<tr>
<th colspan="2">Name</th>
<th colspan="3">Video</th>
<th>Audio</th>
</tr>
<tr>
<th>App Name</th>
<th>Stream Name</th>
<th>Video Codec</th>
<th>Video Width</th>
<th>Video Height</th>
<th>Audio Codec</th>
</tr>
</thead>
<tbody id="groupInfoTable"></tbody>
</table>
<h4>Pub</h4>
<table>
<thead>
<tr>
<th rowspan="2">Session ID</th>
<th rowspan="2">Remote Addr</th>
<th rowspan="2">Start Time</th>
<th colspan="2">Read</th>
<th colspan="2">Write</th>
</tr>
<tr>
<th>Bytes Sum</th>
<th>Bitrate</th>
<th>Bytes Sum</th>
<th>Bitrate</th>
</tr>
</thead>
<tbody id="groupPubTable"></tbody>
</table>
<h4>Pull</h4>
<table>
<thead>
<tr>
<th rowspan="2">Session ID</th>
<th rowspan="2">Protocol</th>
<th rowspan="2">Remote Addr</th>
<th rowspan="2">Start Time</th>
<th colspan="2">Read</th>
<th colspan="2">Write</th>
</tr>
<tr>
<th>Bytes Sum</th>
<th>Bitrate</th>
<th>Bytes Sum</th>
<th>Bitrate</th>
</tr>
</thead>
<tbody id="groupPullTable"></tbody>
</table>
<h4>Subs</h4>
<div>
Sort:
<input type="radio" name="rbSubSort" value="asc" checked> ASC
<input type="radio" name="rbSubSort" value="desc"> DESC
</div>
<table>
<thead>
<tr>
<th rowspan="2">#</th>
<th rowspan="2">Session ID</th>
<th rowspan="2">Protocol</th>
<th rowspan="2">Remote Addr</th>
<th rowspan="2">Start Time</th>
<th colspan="2">Read</th>
<th colspan="2">Write</th>
</tr>
<tr>
<th>Bytes Sum</th>
<th>Bitrate</th>
<th>Bytes Sum</th>
<th>Bitrate</th>
</tr>
</thead>
<tbody id="groupSubTable"></tbody>
</table>
</section>
<footer>
<hr>lal
</footer>
<script>
const allGroupSection = document.getElementById("allGroupSection");
const selAllGroupSortCol = document.getElementById("selAllGroupSortCol");
const rbAllGroupSort = document.getElementsByName("rbAllGroupSort");
const chkAllGroupAuto = document.getElementById("chkAllGroupAuto");
const btnAllGroupRefresh = document.getElementById("btnAllGroupRefresh");
const txtAllGroupInterval = document.getElementById("txtAllGroupInterval");
const lblAllGroupUpdateTime = document.getElementById("lblAllGroupUpdateTime");
const allGroupTable = document.getElementById("allGroupTable");
let allGroupTimer;
const groupSection = document.getElementById("groupSection");
const groupName = document.getElementById("groupName");
const chkGroupAuto = document.getElementById("chkGroupAuto");
const btnGroupRefresh = document.getElementById("btnGroupRefresh");
const txtGroupInterval = document.getElementById("txtGroupInterval");
const lblGroupUpdateTime = document.getElementById("lblGroupUpdateTime");
const groupInfoTable = document.getElementById("groupInfoTable");
const groupPubTable = document.getElementById("groupPubTable");
const groupPullTable = document.getElementById("groupPullTable");
const rbSubSort = document.getElementsByName("rbSubSort");
const groupSubTable = document.getElementById("groupSubTable");
let groupTimer;
init();
function init() {
const params = new URLSearchParams(location.search);
let sort = params.get("sort");
if(sort == "start") {
selAllGroupSortCol.value = "start";
}
let isDesc = params.get("desc");
if(isDesc == 1) {
for(let index = 0; index < rbAllGroupSort.length; index++) {
if(rbAllGroupSort[index].value == "desc") {
rbAllGroupSort[index].checked = true;
break;
}
}
}
let interval = params.get("interval");
if(interval > 0) {
txtAllGroupInterval.value = interval;
}
let isAuto = params.get("auto");
if(isAuto == 1) {
chkAllGroupAuto.checked = true;
onCheckBoxAllGroupAutoClick();
return;
}
requestAllGroup();
}
function StartAllGroupTimer() {
let interval = txtAllGroupInterval.value * 1000;
btnAllGroupRefresh.disabled = "disabled";
allGroupTimer = setInterval(requestAllGroup, interval);
}
function StopAllGroupTimer() {
btnAllGroupRefresh.disabled = false;
clearInterval(allGroupTimer);
}
function onCheckBoxAllGroupAutoClick() {
if(chkAllGroupAuto.checked) {
requestAllGroup();
StartAllGroupTimer();
} else {
StopAllGroupTimer();
}
}
function onChangeTxtAllGroupInterval() {
if(chkAllGroupAuto.checked) {
StopAllGroupTimer();
StartAllGroupTimer();
}
}
function httpAsync(url, method, callback) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if(xmlHttp.readyState == 4)
callback(xmlHttp.status, xmlHttp.responseText);
}
xmlHttp.open(method, url, true);
xmlHttp.send(null);
}
function allGroupSort(a, b) {
const sortCol = selAllGroupSortCol.options[selAllGroupSortCol.selectedIndex].value;
let isDesc = false;
for(let index = 0; index < rbAllGroupSort.length; index++) {
if(rbAllGroupSort[index].checked) {
if(rbAllGroupSort[index].value == "desc") {
isDesc = true;
}
break;
}
}
let data1 = sortCol == "name" ? a["app_name"] : a["pub"]["start_time"];
let data2 = sortCol == "name" ? b["app_name"] : b["pub"]["start_time"];
if(data1 == data2) {
data1 = a["stream_name"];
data2 = b["stream_name"];
}
if(isDesc) {
let tmp = data1;
data1 = data2;
data2 = tmp;
}
return data1 < data2 ? -1 : 1
}
function requestAllGroup() {
httpAsync("/api/stat/all_group", "GET", function(code, resp) {
if(code == 200) {
const obj = JSON.parse(resp);
obj["data"]["groups"].sort(allGroupSort);
for(let index = allGroupTable.rows.length; index > 0; index--) {
allGroupTable.deleteRow(-1);
}
for(let i=0;i<obj["data"]["groups"].length;i++) {
const group = obj["data"]["groups"][i];
const newRow = allGroupTable.insertRow();
newRow.insertCell(0).innerText = i + 1;
newRow.insertCell(1).innerText = group["app_name"];
newRow.insertCell(2).innerText = group["stream_name"];
newRow.insertCell(3).innerText = group["video_codec"];
newRow.insertCell(4).innerText = group["video_width"];
newRow.insertCell(5).innerText = group["video_height"];
newRow.insertCell(6).innerText = group["audio_codec"];
newRow.insertCell(7).innerText = group["pub"]["session_id"];
newRow.insertCell(8).innerText = group["pub"]["remote_addr"];
newRow.insertCell(9).innerText = group["pub"]["start_time"];
newRow.insertCell(10).innerText = group["pub"]["read_bytes_sum"];
newRow.insertCell(11).innerText = group["pub"]["read_bitrate_kbits"];
newRow.insertCell(12).innerText = group["pub"]["wrote_bytes_sum"];
newRow.insertCell(13).innerText = group["pub"]["write_bitrate_kbits"];
let subsCnt = 0;
if(group["subs"]) {
subsCnt = group["subs"].length;
}
newRow.insertCell(14).innerText = subsCnt;
newRow.addEventListener("click", function() {GroupOpen(group["stream_name"]);});
}
lblAllGroupUpdateTime.innerText = (new Date().toString());
}
});
}
function GroupOpen(streamName) {
txtGroupInterval.value = txtAllGroupInterval.value;
chkGroupAuto.checked = chkAllGroupAuto.checked;
for(let i = 0; i < rbAllGroupSort.length; i++) {
if(rbAllGroupSort[i].checked) {
for(let j = 0; j < rbSubSort.length; j++) {
if(rbSubSort[j].value == rbAllGroupSort[i].value) {
rbSubSort[j].checked = true;
}
}
break;
}
}
StopAllGroupTimer();
groupName.innerText = streamName;
allGroupSection.style.display = "none";
groupSection.style.display = "block";
if(chkGroupAuto.checked) {
onCheckBoxGroupAutoClick();
return;
}
requestGroup();
}
function GroupClose() {
txtAllGroupInterval.value = txtGroupInterval.value;
chkAllGroupAuto.checked = chkGroupAuto.checked;
for(let i = 0; i < rbSubSort.length; i++) {
if(rbSubSort[i].checked) {
for(let j = 0; j < rbAllGroupSort.length; j++) {
if(rbAllGroupSort[j].value == rbSubSort[i].value) {
rbAllGroupSort[j].checked = true;
}
}
break;
}
}
StopGroupTimer();
onCheckBoxAllGroupAutoClick();
allGroupSection.style.display = "block";
groupSection.style.display = "none";
}
function onCheckBoxGroupAutoClick() {
if(chkGroupAuto.checked) {
requestGroup();
StartGroupTimer();
} else {
StopGroupTimer();
}
}
function onChangeTxtGroupInterval() {
if(chkGroupAuto.checked) {
StopGroupTimer();
StartGroupTimer();
}
}
function StartGroupTimer() {
let interval = txtGroupInterval.value * 1000;
btnGroupRefresh.disabled = "disabled";
groupTimer = setInterval(requestGroup, interval);
}
function StopGroupTimer() {
btnGroupRefresh.disabled = false;
clearInterval(groupTimer);
}
function subSort(a, b) {
let isDesc = false;
for(let index = 0; index < rbSubSort.length; index++) {
if(rbSubSort[index].checked) {
if(rbSubSort[index].value == "desc") {
isDesc = true;
}
break;
}
}
let data1 = isDesc ? b["session_id"] : a["session_id"];
let data2 = isDesc ? a["session_id"] : b["session_id"];
return data1 < data2 ? -1 : 1
}
function requestGroup() {
httpAsync("/api/stat/group?stream_name="+groupName.innerText, "GET", function(code, resp) {
if(code == 200) {
const obj = JSON.parse(resp);
for(let index = groupInfoTable.rows.length; index > 0; index--) {
groupInfoTable.deleteRow(-1);
}
for(let index = groupPubTable.rows.length; index > 0; index--) {
groupPubTable.deleteRow(-1);
}
for(let index = groupPullTable.rows.length; index > 0; index--) {
groupPullTable.deleteRow(-1);
}
for(let index = groupSubTable.rows.length; index > 0; index--) {
groupSubTable.deleteRow(-1);
}
if(obj["error_code"] > 0) {
return;
}
const infoRow = groupInfoTable.insertRow();
infoRow.insertCell(0).innerText = obj["data"]["app_name"];
infoRow.insertCell(1).innerText = obj["data"]["stream_name"];
infoRow.insertCell(2).innerText = obj["data"]["video_codec"];
infoRow.insertCell(3).innerText = obj["data"]["video_width"];
infoRow.insertCell(4).innerText = obj["data"]["video_height"];
infoRow.insertCell(5).innerText = obj["data"]["audio_codec"];
const pubRow = groupPubTable.insertRow();
pubRow.insertCell(0).innerText = obj["data"]["pub"]["session_id"];
pubRow.insertCell(1).innerText = obj["data"]["pub"]["remote_addr"];
pubRow.insertCell(2).innerText = obj["data"]["pub"]["start_time"];
pubRow.insertCell(3).innerText = obj["data"]["pub"]["read_bytes_sum"];
pubRow.insertCell(4).innerText = obj["data"]["pub"]["read_bitrate_kbits"];
pubRow.insertCell(5).innerText = obj["data"]["pub"]["wrote_bytes_sum"];
pubRow.insertCell(6).innerText = obj["data"]["pub"]["write_bitrate_kbits"];
const pullRow = groupPullTable.insertRow();
pullRow.insertCell(0).innerText = obj["data"]["pull"]["session_id"];
pullRow.insertCell(1).innerText = obj["data"]["pull"]["protocol"];
pullRow.insertCell(2).innerText = obj["data"]["pull"]["remote_addr"];
pullRow.insertCell(3).innerText = obj["data"]["pull"]["start_time"];
pullRow.insertCell(4).innerText = obj["data"]["pull"]["read_bytes_sum"];
pullRow.insertCell(5).innerText = obj["data"]["pull"]["read_bitrate_kbits"];
pullRow.insertCell(6).innerText = obj["data"]["pull"]["wrote_bytes_sum"];
pullRow.insertCell(7).innerText = obj["data"]["pull"]["write_bitrate_kbits"];
if(obj["data"]["subs"]) {
obj["data"]["subs"].sort(subSort);
for(let i = 0; i < obj["data"]["subs"].length; i++) {
const sub = obj["data"]["subs"][i];
const subRow = groupSubTable.insertRow();
subRow.insertCell(0).innerText = i + 1;
subRow.insertCell(1).innerText = sub["session_id"];
subRow.insertCell(2).innerText = sub["protocol"];
subRow.insertCell(3).innerText = sub["remote_addr"];
subRow.insertCell(4).innerText = sub["start_time"];
subRow.insertCell(5).innerText = sub["read_bytes_sum"];
subRow.insertCell(6).innerText = sub["read_bitrate_kbits"];
subRow.insertCell(7).innerText = sub["wrote_bytes_sum"];
subRow.insertCell(8).innerText = sub["write_bitrate_kbits"];
}
}
lblGroupUpdateTime.innerText = (new Date().toString());
}
});
}
</script>
</body>
</html>

@ -10,10 +10,13 @@ package logic
import (
"encoding/json"
"github.com/q191201771/naza/pkg/nazajson"
"html/template"
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/q191201771/naza/pkg/nazajson"
"github.com/q191201771/naza/pkg/nazahttp"
@ -27,6 +30,8 @@ type HttpApiServer struct {
ln net.Listener
}
var webUITpl string
func NewHttpApiServer(addr string, sm *ServerManager) *HttpApiServer {
return &HttpApiServer{
addr: addr,
@ -53,7 +58,7 @@ func (h *HttpApiServer) RunLoop() error {
mux.HandleFunc("/api/ctrl/stop_relay_pull", h.ctrlStopRelayPullHandler)
mux.HandleFunc("/api/ctrl/kick_session", h.ctrlKickSessionHandler)
mux.HandleFunc("/api/ctrl/start_rtp_pub", h.ctrlStartRtpPubHandler)
mux.HandleFunc("/", h.notFoundHandler)
mux.HandleFunc("/", h.webUIHandler)
var srv http.Server
srv.Handler = mux
@ -213,6 +218,36 @@ func (h *HttpApiServer) ctrlStartRtpPubHandler(w http.ResponseWriter, req *http.
// ---------------------------------------------------------------------------------------------------------------------
func (h *HttpApiServer) webUIHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" {
h.notFoundHandler(w, req)
return
}
t, err := template.New("webUI").Parse(webUITpl)
if err != nil {
Log.Errorf("invaild html template: %v", err)
return
}
lalInfo := h.sm.StatLalInfo()
data := map[string]interface{}{
"ServerID": lalInfo.ServerId,
"LalVersion": lalInfo.LalVersion,
"ApiVersion": lalInfo.ApiVersion,
"NotifyVersion": lalInfo.NotifyVersion,
"StartTime": lalInfo.StartTime,
}
for _, item := range strings.Split(lalInfo.BinInfo, ". ") {
if index := strings.Index(item, "="); index != -1 {
k := item[:index]
v := strings.TrimPrefix(strings.TrimSuffix(item[index:], "."), "=")
data[k] = v
}
}
t.Execute(w, data)
}
func (h *HttpApiServer) notFoundHandler(w http.ResponseWriter, req *http.Request) {
Log.Warnf("invalid http-api request. uri=%s, raddr=%s", req.RequestURI, req.RemoteAddr)
}

Loading…
Cancel
Save