diff --git a/ServerLogClient/README.md b/ServerLogClient/README.md
new file mode 100644
index 0000000..a3bd7e4
--- /dev/null
+++ b/ServerLogClient/README.md
@@ -0,0 +1,9 @@
+# Server log monitor
+This application allows to monitor Server Syslog
+using Antunnel protocol. Features:
+* Realtime log monitoring
+* Filter logs by pattern (regular expression), severity
+* Recording last n log messages
+
+## Change logs
+* v0.1.0-b first beta version
\ No newline at end of file
diff --git a/ServerLogClient/build.json b/ServerLogClient/build.json
new file mode 100644
index 0000000..14f6c27
--- /dev/null
+++ b/ServerLogClient/build.json
@@ -0,0 +1,71 @@
+{
+ "name": "ServerLogClient",
+ "targets":{
+ "clean": {
+ "jobs": [
+ {
+ "name": "vfs-rm",
+ "data": ["build/debug","build/release"]
+ }
+ ]
+ },
+ "build": {
+ "require": ["ts"],
+ "jobs":[
+ {
+ "name": "vfs-mkdir",
+ "data": ["build","build/debug","build/release"]
+ },
+ {
+ "name": "ts-import",
+ "data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
+ },
+ {
+ "name": "ts-compile",
+ "data": {
+ "src": ["main.ts"],
+ "dest": "build/debug/main.js"
+ }
+ }
+ ]
+ },
+ "uglify": {
+ "require": ["terser"],
+ "jobs": [
+ {
+ "name":"terser-uglify",
+ "data": ["build/debug/main.js"]
+ }
+ ]
+ },
+ "copy": {
+ "jobs": [
+ {
+ "name": "vfs-cp",
+ "data": {
+ "src": [
+ "scheme.html",
+ "package.json",
+ "README.md",
+ "main.css"
+ ],
+ "dest":"build/debug"
+ }
+ }
+ ]
+ },
+ "release": {
+ "depend": ["clean","build","uglify", "copy"],
+ "require": ["zip"],
+ "jobs": [
+ {
+ "name": "zip-mk",
+ "data": {
+ "src":"build/debug",
+ "dest":"build/release/ServerLogClient.zip"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerLogClient/build/debug/README.md b/ServerLogClient/build/debug/README.md
new file mode 100644
index 0000000..a3bd7e4
--- /dev/null
+++ b/ServerLogClient/build/debug/README.md
@@ -0,0 +1,9 @@
+# Server log monitor
+This application allows to monitor Server Syslog
+using Antunnel protocol. Features:
+* Realtime log monitoring
+* Filter logs by pattern (regular expression), severity
+* Recording last n log messages
+
+## Change logs
+* v0.1.0-b first beta version
\ No newline at end of file
diff --git a/ServerLogClient/build/debug/main.css b/ServerLogClient/build/debug/main.css
new file mode 100644
index 0000000..cbe94df
--- /dev/null
+++ b/ServerLogClient/build/debug/main.css
@@ -0,0 +1,55 @@
+afx-app-window[data-id="ServerLogClient"] p {
+ margin: 0;
+ padding: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ font-family: "Courier New";
+ border-bottom: 1px solid gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] p:hover {
+ background-color: gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.debug{
+ color:green;
+}
+
+/*
+afx-app-window[data-id="ServerLogClient"] p.notice, afx-app-window[data-id="ServerLogClient"] p.info{
+ color:green;
+}*/
+
+afx-app-window[data-id="ServerLogClient"] p.warning {
+ color: orange;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.err {
+ color: orangered;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.crit, afx-app-window[data-id="ServerLogClient"] p.alert {
+ color: orangered;
+ font-weight: bold;
+}
+afx-app-window[data-id="ServerLogClient"] p.emerg {
+ color: red;
+ font-weight: bold;
+}
+afx-app-window[data-id="ServerLogClient"] input {
+ height: 25px;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] {
+ border-bottom: 1px solid gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] afx-label.field-label{
+ text-align: right;
+ padding-top: 5px;
+ padding-right: 3px;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] afx-label.field-label i.label-text{
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/ServerLogClient/build/debug/main.js b/ServerLogClient/build/debug/main.js
new file mode 100644
index 0000000..0279a33
--- /dev/null
+++ b/ServerLogClient/build/debug/main.js
@@ -0,0 +1 @@
+var OS;!function(t){let e;!function(t){class e extends t.BaseApplication{constructor(t){super("ServerLogClient",t)}check(t){return!(!this.filter.record||!this.filter[t.severity_label]||this.filter.pattern&&!t.message.match(this.filter.pattern))}log(t){if(this.check(t)){if(this.logs.length>=this.filter.max_log){let t=this.logs.shift();$(t.el).remove()}t.el=$("
").addClass(t.severity_label),t.el.text(t.message),$(this.log_container).append(t.el),this.log_container.scrollTop=this.log_container.scrollHeight,this.logs.push(t)}}openSession(){this.sub=new Antunnel.Subscriber(this.setting.topic),this.sub.onopen=()=>{console.log("Subscribed")},this.sub.onerror=t=>{this.error(__("Unable to connect to: syslog"),t),this.sub=void 0},this.sub.onmessage=t=>{if(t.data){let e=JSON.parse(new TextDecoder("utf-8").decode(t.data));e.priority&&(e.priority=parseInt(e.priority)),e.severity&&(e.severity=parseInt(e.severity)),e.facility&&(e.facility=parseInt(e.facility)),this.log(e)}},this.sub.onclose=()=>{this.sub=void 0,this.notify(__("Connection closed")),this.quit(!0)},this.tunnel.subscribe(this.sub)}cleanup(){this.sub&&this.sub.close()}checklib(){Antunnel.tunnel?(this.tunnel=Antunnel.tunnel,this.openSession()):this._gui.pushService("Antunnel/AntunnelService").then(t=>{let e=this.systemsetting.system.tunnel_uri;e||(this.error(__("Unable to connect to the tunnel")),this.quit(!0)),Antunnel.init(e).then(t=>{this.notify(__("Tunnel now connected to the server at: {0}",e)),this.tunnel=Antunnel.tunnel,this.openSession()}).catch(t=>{Antunnel.tunnel&&(Antunnel.tunnel.close(),this.error(__("Unable to connect to the tunnel: {0}",t.toString()),t),this.quit(!0))})}).catch(t=>{this.error(__("Unable to run Antunnel service: {0}",t.toString()),t),this.quit(!0)})}main(){if(!Antunnel)return this.error(__("Antunnel library is not available")),void this.quit(!0);this.log_container=this.find("log-container"),this.logs=[],$(this.log_container).css("overflow-y","auto"),this.find("menu-level").items=[{text:__("Default level"),nodes:[{text:__("Debug"),switch:!0,checked:!0,severity:"debug"},{text:__("Notice"),switch:!0,checked:!0,severity:"notice"},{text:__("Info"),switch:!0,checked:!0,severity:"info"},{text:__("Warning"),switch:!0,checked:!0,severity:"warning"},{text:__("Error"),switch:!0,checked:!0,severity:"err"},{text:__("Critical"),switch:!0,checked:!0,severity:"crit"},{text:__("Alert"),switch:!0,checked:!0,severity:"alert"},{text:__("Emergency"),switch:!0,checked:!0,severity:"emerg"}],onchildselect:t=>{let e=t.data.item.data;this.filter[e.severity]=e.checked}}],this.filter={max_log:500,err:!0,emerg:!0,debug:!0,info:!0,notice:!0,warning:!0,crit:!0,alert:!0,pattern:void 0,record:!0};let t=this.find("txt-n-log");t.value=this.filter.max_log.toString(),$(t).on("keyup",e=>{if("Enter"===e.key){let e=parseInt(t.value);if(!isNaN(e))for(this.filter.max_log=e;this.logs.length>e;)this.logs.shift().el.remove();t.value=this.filter.max_log.toString()}}),this.find("btn-clear").onbtclick=()=>{this.logs=[],$(this.log_container).empty()};let e=this.find("txt-reg");$(e).on("keyup",t=>{if("Enter"===t.key){if(""===e.value.trim())return this.filter.pattern=void 0;try{this.filter.pattern=new RegExp(e.value,"g")}catch(t){this.error(__("Invalid regular expression: {0}",t.toString()),t),this.filter.pattern=void 0,e.value=""}}}),this.find("sw-record").onswchange=t=>{this.filter.record=t.data},this.setting.topic?this.checklib():this._gui.openDialog("PromptDialog",{title:__("Enter topic name"),label:__("Please enter Antunnel topic name")}).then(t=>{this.setting.topic=t,this.checklib()})}}t.ServerLogClient=e,e.dependencies=["pkg://Antunnel/main.js"],e.singleton=!0}(e=t.application||(t.application={}))}(OS||(OS={}));
\ No newline at end of file
diff --git a/ServerLogClient/build/debug/package.json b/ServerLogClient/build/debug/package.json
new file mode 100644
index 0000000..3981372
--- /dev/null
+++ b/ServerLogClient/build/debug/package.json
@@ -0,0 +1,18 @@
+{
+ "pkgname": "ServerLogClient",
+ "app":"ServerLogClient",
+ "name":"Server log monitor",
+ "description":"Client for server Syslog monitoring",
+ "info":{
+ "author": "",
+ "email": ""
+ },
+ "version":"0.0.1-b",
+ "category":"System",
+ "iconclass":"bi bi-receipt-cutoff",
+ "mimes":["none"],
+ "dependencies": [
+ "Antunnel@0.1.8-a"
+ ],
+ "locale": {}
+}
\ No newline at end of file
diff --git a/ServerLogClient/build/debug/scheme.html b/ServerLogClient/build/debug/scheme.html
new file mode 100644
index 0000000..5927284
--- /dev/null
+++ b/ServerLogClient/build/debug/scheme.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ServerLogClient/build/release/ServerLogClient.zip b/ServerLogClient/build/release/ServerLogClient.zip
new file mode 100644
index 0000000..c400369
Binary files /dev/null and b/ServerLogClient/build/release/ServerLogClient.zip differ
diff --git a/ServerLogClient/main.css b/ServerLogClient/main.css
new file mode 100644
index 0000000..cbe94df
--- /dev/null
+++ b/ServerLogClient/main.css
@@ -0,0 +1,55 @@
+afx-app-window[data-id="ServerLogClient"] p {
+ margin: 0;
+ padding: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ font-family: "Courier New";
+ border-bottom: 1px solid gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] p:hover {
+ background-color: gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.debug{
+ color:green;
+}
+
+/*
+afx-app-window[data-id="ServerLogClient"] p.notice, afx-app-window[data-id="ServerLogClient"] p.info{
+ color:green;
+}*/
+
+afx-app-window[data-id="ServerLogClient"] p.warning {
+ color: orange;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.err {
+ color: orangered;
+}
+
+afx-app-window[data-id="ServerLogClient"] p.crit, afx-app-window[data-id="ServerLogClient"] p.alert {
+ color: orangered;
+ font-weight: bold;
+}
+afx-app-window[data-id="ServerLogClient"] p.emerg {
+ color: red;
+ font-weight: bold;
+}
+afx-app-window[data-id="ServerLogClient"] input {
+ height: 25px;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] {
+ border-bottom: 1px solid gray;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] afx-label.field-label{
+ text-align: right;
+ padding-top: 5px;
+ padding-right: 3px;
+}
+
+afx-app-window[data-id="ServerLogClient"] afx-hbox[data-id="top-header"] afx-label.field-label i.label-text{
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/ServerLogClient/main.ts b/ServerLogClient/main.ts
new file mode 100644
index 0000000..e6d41aa
--- /dev/null
+++ b/ServerLogClient/main.ts
@@ -0,0 +1,335 @@
+namespace OS {
+
+ export namespace application {
+ declare var Antunnel: any;
+ interface SyslogMessage {
+ timestamp: string;
+ tag: string;
+ severity_label: string;
+ severity: number;
+ relayip?: string;
+ relayhost?: string;
+ program: string;
+ priority: number;
+ message: string;
+ logsource?: string;
+ hostname?: string;
+ facility_label: string;
+ facility: number;
+ end_msg?: string;
+ type?: string;
+ el: JQuery;
+ };
+ interface SyslogFilter {
+ max_log: number;
+ err: boolean;
+ debug: boolean;
+ info: boolean;
+ notice: boolean;
+ warning: boolean;
+ crit: boolean;
+ alert: boolean;
+ emerg: boolean;
+ pattern?: RegExp;
+ record: boolean;
+ };
+ /**
+ *
+ * @class ServerLogClient
+ * @extends {BaseApplication}
+ */
+ export class ServerLogClient extends BaseApplication {
+ private tunnel: any;
+ private sub: GenericObject;
+ private log_container: HTMLDivElement;
+ private filter: SyslogFilter;
+ private logs: SyslogMessage[];
+ constructor(args: AppArgumentsType[]) {
+ super("ServerLogClient", args);
+ }
+ private check(msg: SyslogMessage): boolean
+ {
+ if(!this.filter.record)
+ {
+ return false;
+ }
+ // filter by severity
+ if(!this.filter[msg.severity_label])
+ {
+ return false;
+ }
+ // filter by regex
+ if(this.filter.pattern)
+ {
+ if(msg.message.match(this.filter.pattern))
+ {
+ return true;
+ }
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ private log(msg: SyslogMessage): void {
+ if(!this.check(msg))
+ {
+ return;
+ }
+ // if the log
+ if(this.logs.length >= this.filter.max_log)
+ {
+ let logel = this.logs.shift();
+ $(logel.el).remove();
+ }
+ msg.el = $("").addClass(msg.severity_label);
+ msg.el.text(msg.message);
+ $(this.log_container).append(msg.el);
+ this.log_container.scrollTop = this.log_container.scrollHeight;
+ this.logs.push(msg);
+ }
+
+ private openSession(): void {
+ this.sub = new Antunnel.Subscriber(this.setting.topic);
+ this.sub.onopen = () => {
+ console.log("Subscribed");
+ };
+
+ this.sub.onerror = (e) => {
+ this.error(__("Unable to connect to: syslog"), e);
+ this.sub = undefined;
+ }
+
+ this.sub.onmessage = (e: GenericObject) => {
+ if(e.data)
+ {
+ let data = JSON.parse(new TextDecoder("utf-8").decode(e.data));
+ if(data.priority)
+ {
+ data.priority = parseInt(data.priority);
+ }
+ if(data.severity)
+ {
+ data.severity = parseInt(data.severity);
+ }
+ if(data.facility)
+ {
+ data.facility = parseInt(data.facility);
+ }
+ this.log(data as SyslogMessage);
+ }
+ }
+
+ this.sub.onclose = () =>
+ {
+ this.sub = undefined;
+ this.notify(__("Connection closed"));
+ this.quit(true);
+ }
+
+ this.tunnel.subscribe(this.sub);
+ }
+
+ cleanup(): void {
+ if(this.sub)
+ this.sub.close();
+ }
+ private checklib(): void {
+ if(!Antunnel.tunnel)
+ {
+ this._gui
+ .pushService("Antunnel/AntunnelService")
+ .then((d) => {
+ let uri = (this.systemsetting.system as any).tunnel_uri as string;
+ if(!uri)
+ {
+ this.error(__("Unable to connect to the tunnel"));
+ this.quit(true);
+ }
+ Antunnel
+ .init(uri)
+ .then((t) =>{
+ this.notify(__("Tunnel now connected to the server at: {0}", uri));
+ this.tunnel = Antunnel.tunnel;
+ this.openSession();
+ })
+ .catch((e) => {
+ if(Antunnel.tunnel)
+ {
+ Antunnel.tunnel.close();
+ this.error(__("Unable to connect to the tunnel: {0}", e.toString()), e);
+ this.quit(true);
+ }
+ });
+ })
+ .catch((e) => {
+ this.error(__("Unable to run Antunnel service: {0}",e.toString()),e);
+ this.quit(true);
+ });
+ }
+ else
+ {
+ this.tunnel = Antunnel.tunnel;
+ this.openSession();
+ }
+ }
+
+ main(): void {
+ if(!Antunnel)
+ {
+ this.error(__("Antunnel library is not available"));
+ this.quit(true);
+ return;
+ }
+ this.log_container = this.find("log-container") as HTMLDivElement;
+ this.logs = [];
+ $(this.log_container)
+ .css("overflow-y", "auto");
+ let menu = this.find("menu-level") as GUI.tag.MenuTag;
+ menu.items = [
+ {
+ text: __("Default level"),
+ nodes: [
+ {
+ text: __("Debug"),
+ switch: true,
+ checked: true,
+ severity: "debug"
+ },
+ {
+ text: __("Notice"),
+ switch: true,
+ checked: true,
+ severity: "notice"
+ },
+ {
+ text: __("Info"),
+ switch: true,
+ checked: true,
+ severity: "info"
+ },
+ {
+ text: __("Warning"),
+ switch: true,
+ checked: true,
+ severity: "warning"
+ },
+ {
+ text: __("Error"),
+ switch: true,
+ checked: true,
+ severity: "err"
+ },
+ {
+ text: __("Critical"),
+ switch: true,
+ checked: true,
+ severity: "crit"
+ },
+ {
+ text: __("Alert"),
+ switch: true,
+ checked: true,
+ severity: "alert"
+ },
+ {
+ text: __("Emergency"),
+ switch: true,
+ checked: true,
+ severity: "emerg"
+ }
+ ],
+ onchildselect: (e) => {
+ let data = e.data.item.data;
+ this.filter[data.severity] = data.checked;
+ }
+ }
+ ];
+
+ this.filter = {
+ max_log: 500,
+ err: true,
+ emerg: true,
+ debug: true,
+ info: true,
+ notice: true,
+ warning: true,
+ crit: true,
+ alert: true,
+ pattern: undefined,
+ record: true
+ };
+ let txtnlog = this.find("txt-n-log") as HTMLInputElement;
+ txtnlog.value = this.filter.max_log.toString();
+ $(txtnlog).on("keyup", (e) =>{
+ if(e.key === "Enter")
+ {
+ let val = parseInt(txtnlog.value);
+ if(!isNaN(val))
+ {
+ this.filter.max_log = val;
+ // truncate the log
+ while(this.logs.length > val)
+ {
+ let m = this.logs.shift();
+ m.el.remove();
+ }
+ }
+ txtnlog.value = this.filter.max_log.toString();
+ }
+ });
+ let btn = this.find("btn-clear") as GUI.tag.ButtonTag;
+ btn.onbtclick = () => {
+ this.logs = [];
+ $(this.log_container).empty();
+ };
+ let txtreg = this.find("txt-reg") as HTMLInputElement;
+ $(txtreg).on("keyup", (e) => {
+ if(e.key === "Enter")
+ {
+ if(txtreg.value.trim() === "")
+ {
+ return this.filter.pattern = undefined;
+ }
+
+ try{
+ this.filter.pattern = new RegExp(txtreg.value,"g");
+ }
+ catch(e)
+ {
+ this.error(__("Invalid regular expression: {0}", e.toString()),e);
+ this.filter.pattern = undefined;
+ txtreg.value = "";
+ }
+ }
+ });
+ let sw = this.find("sw-record") as GUI.tag.SwitchTag;
+ sw.onswchange = (e) => {
+ this.filter.record = e.data;
+ }
+ if(!this.setting.topic)
+ {
+ this._gui.openDialog("PromptDialog", {
+ title: __("Enter topic name"),
+ label: __("Please enter Antunnel topic name")
+ })
+ .then((v) =>
+ {
+ this.setting.topic = v;
+ this.checklib();
+ });
+ }
+ else
+ {
+ this.checklib();
+ }
+
+ }
+ }
+ ServerLogClient.dependencies = [
+ "pkg://Antunnel/main.js"
+ ];
+ ServerLogClient.singleton = true;
+ }
+}
\ No newline at end of file
diff --git a/ServerLogClient/package.json b/ServerLogClient/package.json
new file mode 100644
index 0000000..3981372
--- /dev/null
+++ b/ServerLogClient/package.json
@@ -0,0 +1,18 @@
+{
+ "pkgname": "ServerLogClient",
+ "app":"ServerLogClient",
+ "name":"Server log monitor",
+ "description":"Client for server Syslog monitoring",
+ "info":{
+ "author": "",
+ "email": ""
+ },
+ "version":"0.0.1-b",
+ "category":"System",
+ "iconclass":"bi bi-receipt-cutoff",
+ "mimes":["none"],
+ "dependencies": [
+ "Antunnel@0.1.8-a"
+ ],
+ "locale": {}
+}
\ No newline at end of file
diff --git a/ServerLogClient/scheme.html b/ServerLogClient/scheme.html
new file mode 100644
index 0000000..5927284
--- /dev/null
+++ b/ServerLogClient/scheme.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages.json b/packages.json
index 862c44f..e2d172f 100644
--- a/packages.json
+++ b/packages.json
@@ -329,6 +329,16 @@
"dependencies": ["libwvnc@0.1.2-a"],"category":"Internet","icon":"icon.png","mimes":["none"],
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteDesktop/build/release/RemoteDesktop.zip"
},
+ {
+ "pkgname": "ServerLogClient",
+ "name": "Server log monitor",
+ "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/ServerLogClient/README.md",
+ "category": "System",
+ "author": "",
+ "version": "0.0.1-b",
+ "dependencies": ["Antunnel@0.1.8-a"],
+ "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/ServerLogClient/build/release/ServerLogClient.zip"
+ },
{
"pkgname": "ShaderPlayground",
"name": "OpenGL Shader Playground",