From ecc54322a3d5396b054f99291da2ce3e316d68e7 Mon Sep 17 00:00:00 2001 From: lxsang Date: Sat, 18 Sep 2021 13:13:16 +0200 Subject: [PATCH] Add server log monitoring tols --- ServerLogClient/README.md | 9 + ServerLogClient/build.json | 71 ++++ ServerLogClient/build/debug/README.md | 9 + ServerLogClient/build/debug/main.css | 55 +++ ServerLogClient/build/debug/main.js | 1 + ServerLogClient/build/debug/package.json | 18 + ServerLogClient/build/debug/scheme.html | 17 + .../build/release/ServerLogClient.zip | Bin 0 -> 2984 bytes ServerLogClient/main.css | 55 +++ ServerLogClient/main.ts | 335 ++++++++++++++++++ ServerLogClient/package.json | 18 + ServerLogClient/scheme.html | 17 + packages.json | 10 + 13 files changed, 615 insertions(+) create mode 100644 ServerLogClient/README.md create mode 100644 ServerLogClient/build.json create mode 100644 ServerLogClient/build/debug/README.md create mode 100644 ServerLogClient/build/debug/main.css create mode 100644 ServerLogClient/build/debug/main.js create mode 100644 ServerLogClient/build/debug/package.json create mode 100644 ServerLogClient/build/debug/scheme.html create mode 100644 ServerLogClient/build/release/ServerLogClient.zip create mode 100644 ServerLogClient/main.css create mode 100644 ServerLogClient/main.ts create mode 100644 ServerLogClient/package.json create mode 100644 ServerLogClient/scheme.html 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 0000000000000000000000000000000000000000..c40036911a81c5b60fe9767237b69395fcf1f7e5 GIT binary patch literal 2984 zcmZ{mc{G%5AIE30gl0-4Wz8CD3<+VZFGBWxZ;Y{wn1{(85fWn=`x5cQc&y1*Qnr*W zN+@K;P7K)%8Szf0cfe0S*ga&GLdA@OTv%Kv@iyG-Q5?>B97!LQcvH(~ zT4%-Pi!3GKm`ssCDo%jfTuvcwIt47s`5W6YikeQMliRRuyRKij+GAF1gKF~Q_T=Ir zO%9blvCh@?I_`eHvotWi-XzAs;c3$Mgc8+1EavW4T0{k;;cpvW5q=gF0pt0}Si_Xb z7^7}TS`6LL=W2jg%D<@F9lQCnIsSEt$JY_F(#?192BY_>mwgYV=QOoG<)$dQoCG%l z>+-}?co{EGYZk9)Sbwru{~=oyZ&ZW-CLCVNP~*pxstoEg=Vi)IkUXztr~(PoL{!(76u#RWtHqmHAU9|Bz3R zQJM>*i;so^0L=fDkHKYF+hfP&IS-|3Pcw)JN(G;XkDB9QSDrz&%=-AKCJanh_{JSP z^o5gCxswiQP(AWWu_4t3KNkp737lZ#AYWcc^RL3C6WzK`vBK}qRynYE2|t`-*ElEt zX4@J$tz{{Z!^W&MM||!<;Wd~&aoaHxxdG}!WUKY|XQUR&7!eeHaBV8b%0x7L;*f8z zs$a5`WJtTvZD6i~NK|M=Vj}l$kfbwgBAdcOE(}?KmW&GujN#(XAG=bfF^F z;-&BAyi#5uD@2*0P+)+E)bhSlC1A!^FScDL8*=MahL`Kz@J=}6yQK@SHu<@+Bw1zD zhP36K4cJ22rFbd#n*kx>j}<-QU0uF7M~S;P>lnz&u$Hx-&MFsDW3cVPsbg^Fk~
)ejRLQqsVH)5M8l z8!K6?rO=c5;UxRcAnJ$M8)a>JMcs?soHE$XAb5$~`7;yPeNlUu?Liio6eD{kyUZXV z4cu$%x|_N8PQ;IpOU|l&2umJ)&a{(||7b|F&;x~_h56zPH;|X%f$yv5qLMjdW7wuI zj0_d@_{K~wp7_B#*xNn$W9=Xhg72&QQR!RX6%1;AfoQT%_(XoJTdfVVj9ukI-FeZ) zd~{XtTjE+?#h~W1pK+cb{j5tPp;%JIEi=F1xw3AlE3@~!;%@tmT`${d{Z8sq`j9@# z8vV4wv@fq<_iND8%KmUs>u5@p#6h#WFmS8~c{-dk=kCI8<|DiPQU3;=g|?Zb?I$!3 zp-rU%Ny=)bSbbFW)eir zsnlb7z0PUv0as{*9(kx?Jdsdt?$hsc&gxk{(%SYPof#w=3o-H(9sr z&yOTG!gJ7t8F>&oHsC|f&pW|6gy1}oQzdO3Ijx#kN4mHEoQBDtvJ_`uopk;jRzEvZ z>8+0ZP!#2+noHCSniV$M8Gf;Iy<}JO^ZFpCync9=L7oyne(jdCwZ(a@N6BiQLJ#W_ zR`talTi0kF@AE%8#t^7>4(8qq%Y)C(&7lX}L)G}+i&?2B4^ZtpxZqmkL*X(7C>6Q7Jw13h?YQXn*M^d^ z-0+XF{`*iLuJCI|$8PBOj3CmBU&E3b<7n{g+;CZzr`OyoO zRwA=n4TomH*(2aT=TEryvnevYsx*3aC+X`SNR$`S9Sy_aus;3-1hzwv9smwl-#Xa$ zSOsG~H`sSx0*kqaNRlqLrCff0)I6FkVldXYJIkMaN=RVo9z=q=s2xwBtc4q>P2&E#xrikhc-grBwC6IHz7ih%ddi8aVE+9 zoSQh=WdB@3F(!b=rw(oWjbdZPKaL8;?AujXf;y0PfBu!-hM;4w`!+G{l^@-LcIZ7ut+VG6@?+C<%w}`s^fK;7 zFvm5#J;{`4gE-g)_UMfIa_D9>ehqEJXGX6vcP{7dn6V9U?8cutTYTq5=-z{ps0X7( z+KQ*qy`rMcoHoR5|K^fnkgZkr;JMb*yw}r&h-u41>Gq-;!u?!t5pk2H@B7tD{0Ahf z6Bn+odUAXk7+wXR8yFbl?hD3yY-5$*M4;I08DHD#yym?o^*U%!Q%%mm>gr~v6Z63L zz7468La;wehy!z~^viZr6=rSLcU6W z^7c+JyF$_@5LV=@C2*mbcjAKNmvKbRLM9A_Stwa4&}c1$C+-bR2a~C1@bRM)9$7@s zi}7xuff0{GSi+zKJ-migiptj%F;@j)$AU`ug_py#9=cZ6flXH{wun7kL$tf*bpj>^ zKoBS3|Hn_d{l7i`4Wq}Y; + }; + 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",