From 0b3c61e1a64522fff4632ccc1c1d70f62b97f253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 31 Jul 2025 11:40:44 +0200 Subject: [PATCH] Webmine --- .../webmine/block/activator/blue_false.png | Bin 0 -> 310 bytes .../webmine/block/activator/blue_null.png | Bin 0 -> 370 bytes .../webmine/block/activator/blue_true.png | Bin 0 -> 300 bytes .../webmine/block/activator/cyan_false.png | Bin 0 -> 311 bytes .../webmine/block/activator/cyan_null.png | Bin 0 -> 364 bytes .../webmine/block/activator/cyan_true.png | Bin 0 -> 299 bytes .../webmine/block/activator/green_false.png | Bin 0 -> 310 bytes .../webmine/block/activator/green_null.png | Bin 0 -> 368 bytes .../webmine/block/activator/green_true.png | Bin 0 -> 302 bytes .../webmine/block/activator/magenta_false.png | Bin 0 -> 311 bytes .../webmine/block/activator/magenta_null.png | Bin 0 -> 373 bytes .../webmine/block/activator/magenta_true.png | Bin 0 -> 313 bytes .../webmine/block/activator/red_false.png | Bin 0 -> 311 bytes .../webmine/block/activator/red_null.png | Bin 0 -> 371 bytes .../webmine/block/activator/red_true.png | Bin 0 -> 317 bytes .../webmine/block/activator/yellow_false.png | Bin 0 -> 311 bytes .../webmine/block/activator/yellow_null.png | Bin 0 -> 373 bytes .../webmine/block/activator/yellow_true.png | Bin 0 -> 316 bytes .../webmine/block/observer/blue_false.png | Bin 0 -> 268 bytes .../webmine/block/observer/blue_null.png | Bin 0 -> 349 bytes .../webmine/block/observer/blue_true.png | Bin 0 -> 257 bytes .../webmine/block/observer/cyan_false.png | Bin 0 -> 266 bytes .../webmine/block/observer/cyan_null.png | Bin 0 -> 343 bytes .../webmine/block/observer/cyan_true.png | Bin 0 -> 255 bytes .../webmine/block/observer/green_false.png | Bin 0 -> 270 bytes .../webmine/block/observer/green_null.png | Bin 0 -> 347 bytes .../webmine/block/observer/green_true.png | Bin 0 -> 253 bytes .../webmine/block/observer/magenta_false.png | Bin 0 -> 267 bytes .../webmine/block/observer/magenta_null.png | Bin 0 -> 345 bytes .../webmine/block/observer/magenta_true.png | Bin 0 -> 270 bytes .../webmine/block/observer/red_false.png | Bin 0 -> 269 bytes .../webmine/block/observer/red_null.png | Bin 0 -> 343 bytes .../webmine/block/observer/red_true.png | Bin 0 -> 265 bytes .../webmine/block/observer/yellow_false.png | Bin 0 -> 269 bytes .../webmine/block/observer/yellow_null.png | Bin 0 -> 351 bytes .../webmine/block/observer/yellow_true.png | Bin 0 -> 266 bytes src/main/angular/src/app/app.routes.ts | 2 +- src/main/angular/src/app/crud/CrudHelpers.ts | 54 +++++++++---- .../angular/src/app/server-list/Server.ts | 28 ------- .../server-list/server-list.component.html | 34 -------- .../src/app/{server-list => server}/Mode.ts | 0 src/main/angular/src/app/server/Server.ts | 73 ++++++++++++++++++ .../server/list/server-list.component.html | 52 +++++++++++++ .../list}/server-list.component.less | 26 ++++++- .../list}/server-list.component.ts | 12 ++- .../{server-list => server}/server.service.ts | 6 +- src/main/angular/src/styles.less | 4 + .../de/ph87/mc/server/ChannelInstanceDto.java | 22 ++++++ .../java/de/ph87/mc/server/EConsumer.java | 8 ++ .../ph87/mc/server/InvalidChannelSignal.java | 12 +++ .../java/de/ph87/mc/server/Properties.java | 2 +- src/main/java/de/ph87/mc/server/Server.java | 54 +++++++++++-- .../java/de/ph87/mc/server/ServerDto.java | 20 ++++- .../java/de/ph87/mc/server/ServerService.java | 57 +++++++++++++- src/main/java/de/ph87/mc/webmine/Channel.java | 5 ++ .../de/ph87/mc/webmine/WebmineController.java | 57 ++++++++++++++ 56 files changed, 429 insertions(+), 99 deletions(-) create mode 100644 src/main/angular/public/webmine/block/activator/blue_false.png create mode 100644 src/main/angular/public/webmine/block/activator/blue_null.png create mode 100644 src/main/angular/public/webmine/block/activator/blue_true.png create mode 100644 src/main/angular/public/webmine/block/activator/cyan_false.png create mode 100644 src/main/angular/public/webmine/block/activator/cyan_null.png create mode 100644 src/main/angular/public/webmine/block/activator/cyan_true.png create mode 100644 src/main/angular/public/webmine/block/activator/green_false.png create mode 100644 src/main/angular/public/webmine/block/activator/green_null.png create mode 100644 src/main/angular/public/webmine/block/activator/green_true.png create mode 100644 src/main/angular/public/webmine/block/activator/magenta_false.png create mode 100644 src/main/angular/public/webmine/block/activator/magenta_null.png create mode 100644 src/main/angular/public/webmine/block/activator/magenta_true.png create mode 100644 src/main/angular/public/webmine/block/activator/red_false.png create mode 100644 src/main/angular/public/webmine/block/activator/red_null.png create mode 100644 src/main/angular/public/webmine/block/activator/red_true.png create mode 100644 src/main/angular/public/webmine/block/activator/yellow_false.png create mode 100644 src/main/angular/public/webmine/block/activator/yellow_null.png create mode 100644 src/main/angular/public/webmine/block/activator/yellow_true.png create mode 100644 src/main/angular/public/webmine/block/observer/blue_false.png create mode 100644 src/main/angular/public/webmine/block/observer/blue_null.png create mode 100644 src/main/angular/public/webmine/block/observer/blue_true.png create mode 100644 src/main/angular/public/webmine/block/observer/cyan_false.png create mode 100644 src/main/angular/public/webmine/block/observer/cyan_null.png create mode 100644 src/main/angular/public/webmine/block/observer/cyan_true.png create mode 100644 src/main/angular/public/webmine/block/observer/green_false.png create mode 100644 src/main/angular/public/webmine/block/observer/green_null.png create mode 100644 src/main/angular/public/webmine/block/observer/green_true.png create mode 100644 src/main/angular/public/webmine/block/observer/magenta_false.png create mode 100644 src/main/angular/public/webmine/block/observer/magenta_null.png create mode 100644 src/main/angular/public/webmine/block/observer/magenta_true.png create mode 100644 src/main/angular/public/webmine/block/observer/red_false.png create mode 100644 src/main/angular/public/webmine/block/observer/red_null.png create mode 100644 src/main/angular/public/webmine/block/observer/red_true.png create mode 100644 src/main/angular/public/webmine/block/observer/yellow_false.png create mode 100644 src/main/angular/public/webmine/block/observer/yellow_null.png create mode 100644 src/main/angular/public/webmine/block/observer/yellow_true.png delete mode 100644 src/main/angular/src/app/server-list/Server.ts delete mode 100644 src/main/angular/src/app/server-list/server-list.component.html rename src/main/angular/src/app/{server-list => server}/Mode.ts (100%) create mode 100644 src/main/angular/src/app/server/Server.ts create mode 100644 src/main/angular/src/app/server/list/server-list.component.html rename src/main/angular/src/app/{server-list => server/list}/server-list.component.less (68%) rename src/main/angular/src/app/{server-list => server/list}/server-list.component.ts (68%) rename src/main/angular/src/app/{server-list => server}/server.service.ts (72%) create mode 100644 src/main/java/de/ph87/mc/server/ChannelInstanceDto.java create mode 100644 src/main/java/de/ph87/mc/server/EConsumer.java create mode 100644 src/main/java/de/ph87/mc/server/InvalidChannelSignal.java create mode 100644 src/main/java/de/ph87/mc/webmine/Channel.java create mode 100644 src/main/java/de/ph87/mc/webmine/WebmineController.java diff --git a/src/main/angular/public/webmine/block/activator/blue_false.png b/src/main/angular/public/webmine/block/activator/blue_false.png new file mode 100644 index 0000000000000000000000000000000000000000..1b253e17c8d963eb502bebaf37e180c77e4975f5 GIT binary patch literal 310 zcmV-60m=S}P)GMuZMVu`kIV3@d09NtisC1r5 zyrL0A2mpsd9I+fg`}$Vg0)RDlE({%z0D5r-@IEG}5I4Y`xL25g-2kh^-wVKr#F;;1 zJ?}x)xUZn?ZaoAJ^u{#6ofBFgaF-!AczS^b@JJ_Yop=Pm;sQh9fB9p?Nu^zdlzidm zI0B)Gv%}`0bn8gg+PrNj^)?#IdUYhT91>ac0sI|^*cjb=0n4Q{5MMn~&;S4c07*qo IM6N<$g6esGW&i*H literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/blue_null.png b/src/main/angular/public/webmine/block/activator/blue_null.png new file mode 100644 index 0000000000000000000000000000000000000000..54812cb5d6798e41bff8304ec23cd00dc49a5e82 GIT binary patch literal 370 zcmV-&0ge8NP)hbA4Ul3dK|l6NG@|FbGww<=h%L4SFdFIN$HVpfXRWQt+wKS0IWM`5H)6r Q`v3p{07*qoM6N<$f@VRM_y7O^ literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/blue_true.png b/src/main/angular/public/webmine/block/activator/blue_true.png new file mode 100644 index 0000000000000000000000000000000000000000..31270f9b55545f956c3a07f92e15b96265102340 GIT binary patch literal 300 zcmV+{0n`48P)lv9P-vlFHTpW4XsET;zwfq(7~nYeFq|i~7vOmc@V;43 z#7`j)Na5aX!xCM99uPMG2cp8WDhz@kTmYHk0)rqRfTy@ft|??em;m15EHjGLAZ`JS zNy`DE2~j+IVx@wrfR#{76rE^b^7>lC2g0mtzj2vl4Yc3(=j yUdiSrvgJ~m>3C?3+XE<3_WKL)Qy?p0#kvnBcQg>Lr0ZG$0000^0j%PmniJn@ zw{%4_h!6k{g*ak6fcEvRxCH=fMlMVpkN|pd2Jk*6s1P^6NZc#Tz;1wD;?D)(MB>a} zv7Yy!YTQ@QcDEh^2YO=}VC00>2i#?d4W3@00o>9FTPJP-u(-ew_}~5*aZ+hlAthh< zIgUVR;_R?_C_OrowKg9cO1+K7vR)m@EQdtaya0a(A~r_%{s6kfG!TyD6Hx#F002ov JPDHLkV1fh8dC33( literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/cyan_null.png b/src/main/angular/public/webmine/block/activator/cyan_null.png new file mode 100644 index 0000000000000000000000000000000000000000..4fd272e2f3e6eec76691a10653b1444bdb414b3c GIT binary patch literal 364 zcmV-y0h9iTP)z>Uf=18`iWR3${!fhs`Zgh1SjnzwW-V7dXsb{1qYcEy5# zW8DB;b0jYzTE(Sj{tsX&|FZ2|N|0-+?1Zm1A$}DE7=|*R*n-OJ8-nsR{j@TnrINZggoW|0000< KMNUMnLSTZ+=bNAa literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/cyan_true.png b/src/main/angular/public/webmine/block/activator/cyan_true.png new file mode 100644 index 0000000000000000000000000000000000000000..6528b3ec7c7b984593a78e9d876b19cffb888cff GIT binary patch literal 299 zcmV+`0o4A9P)lv9P-vlFHTpW4XsET;fA6-37~s?QFno#H3-CMzc;Boi z;-`=Yq;Lm@r4!Ht;s)SARCrc}K@fxsAX8jm5CjD96c@=gg)9gYz+0SUMzI>iEr2m; zIY2Zaif2!(R8SSL5^9N}(@eH**$FiOn002ovPDHLkV1o8xah3o8 literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/green_false.png b/src/main/angular/public/webmine/block/activator/green_false.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f528c13031dd89ebc873c820e44cfd4dc94687 GIT binary patch literal 310 zcmV-60m=S}P)GMuZMVu`kIV3@d09Ns%9(1Nt zyrL0A2mpsd9I+fg`}$Vg0)RDlE({%z0D5r-@IEG}5I4Y`xL25g-2kh^-wVKr#F;;1 zJ?}x)xUZn?ZaoAJ^u{#6ofBFgaF-!AczS^b@JJ_Yop=Pm;sQh9fB9p?Nu^zdlzidm zI0B)Gv%}`0bn8gg+PrNj^)?#IdUYhT91>ac0sI|^*cjb=0n4Q{5cTnMYybcN07*qo IM6N<$f`l10o^fb%-= zO8L1dwSl}@L#rn%mg)c&@L&iQj?q3eAs{3XgJ`ihwpoO7;Ye8#&RqeBL@l{#)_{aa z1Z_w4;{+CI;`qOM3^8FPt3a^jD~E(Qc3A2&Ccr4pf+C=F0^;|0r#|%NT+GuTwgbSg z41&e+X$CMrn*dh3mzfs=kF;|hBY>P&NL3C|bf5^3Il&Om1i)iDg!KWKW&pNb3Q8&I ziiH5jngL|a5j=<3D$X_Ye*jbZr(@@mf~CrqGW&TE%3g5jG2}j7YOm;qhc==w0FPEY zLu}}9n6mq2grKh1vFn56G*2?OM``^o+h<1gNw$wnIm!hvJ8-nsR{S@tJ7^G@D350V O0000Nkl|IO5_1?18^WJJgdSW2*L%BDK0Pw0s?r7i{zR@7K91lEzUBdSPkM4 zz?ifgAes=xvnN(6s0vsKwM5ZjB3rlYgc<;HwHJc(hXJaJGvqq~G$VOsALCLASltZ` zs1;<`17nie59P;DjA9L-zcwNdz~~92K&ll-AUL*s7|EWazq3vO>K$+#KY>8SMPc`) zbnBIDZX#PQrJ0V0*0?=@5@o-C06zt?5>~9c048@d5EviLxBvhE07*qoM6N<$f|&Ml A_5c6? literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/magenta_false.png b/src/main/angular/public/webmine/block/activator/magenta_false.png new file mode 100644 index 0000000000000000000000000000000000000000..cb7004f62dae99f07d8eb5a88f05530149250c24 GIT binary patch literal 311 zcmV-70m%M|P)Zb@I?eMCE+8MFPNa99sZ#&h!F2&jI93 z3Qs+`YJi1Hk((wZm`Y>;F#}+sCfvFL5DmfvFeA<|8iWd95ogOs4oMIqfK|MF3Z-un zQ#67I0pOz$M=S@>{=OEs0Q}cKP)|-3`NToXFR2GS42LutS~6xoXlh-#zdk2@mmUph^+ez8wLQ=Gz|fm=b0{m^E&W! z`MG6k19{U%uAa15SqHFy2Sc#1wfdnk1|f+UBo~Wgt3@amddiA$>Iy(4YROHt1|)R?`F9uLRy8xoy%gjpww{spIK+Y?qDupOIPy|SwUZvmFx>#bb|Dz)30E`@ zIMxjybMAwu5RKwoGyey$q<=Yfo87bDjV^qYg0;%lf}>v-VblwbJcit-OYIfi@X$uI z0T|JW14KiI!<4UIMhH6eIv)BUIn9%7*rT-ZLfbv9`Xt+}Pzi7W1P6|`T8sYz>&sve TA!owE00000NkvXXu0mjfYRH%M literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/magenta_true.png b/src/main/angular/public/webmine/block/activator/magenta_true.png new file mode 100644 index 0000000000000000000000000000000000000000..2b6b913ee6c0e0e1f43c4072609177fe74702445 GIT binary patch literal 313 zcmV-90mlA`P)_AH=uQ%Juy>3R6q+T1@ca_TDoZ`lmIMOqYx$j$2%}URPs`eEdeAWp|bbqmK4ys zn>C{LDDyvh7&X%^>;hrq_zVQnxXgWE z{C72}`&);vWHS({0agJ%|K0FO+<(uqd^EG{qv-pdaoE-Eb*Qt?e6 z$1@O`I6G{FV=H^akptjZ{BrxsncG0uP2@;bqY`jkQQ=41=U{ac;E<juqz485MxUSx_XDPLR#xXzVnpv4~4S)C0h; z2!h4w69XuqO#s2_W#y%STR9H`ka3w(#SmEsvH-CY4B-GgOl7MArWwH7E(8NT;flrq z$C?3T%_(^dQ7O(n^M3$q`PXf?Sv~t*>B3hf_;lHN*3&q<34^oX$Zd$lZMNc>2hj$g z$5HGdDo!|r{QNRP(yrHW*9XaEo@Ct~rIi=j?y%yMY_~!o!42RYINEA0{|h{OU=VKt R2@U`N002ovPDHLkV1nVppb!86 literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/red_true.png b/src/main/angular/public/webmine/block/activator/red_true.png new file mode 100644 index 0000000000000000000000000000000000000000..91b54df6dba3fc372e10ad30572259eedbe5d0dc GIT binary patch literal 317 zcmV-D0mA-?P)byn0)wS#O}5GYx$qoDAchdq`Frh)1dz7v6@a+!;SaE$0#rBY zv-rv6fs!~S`O(BkqMptMj12?OK`n8w3V~=45Wr0G0;55w07&vGIa3-5LIgmQlg!## z4&oL-8`K@Z8c;h=o*1dXDj)^q0$GPiDc!IWasZmEQHUJ>@(v8(l{}YYNdV4BsO;^z zDFvkNrVXeOq}c;MH|YAj`!lRY5hmZGjR*l~J)tQu)ru_;dH#D3qiVW|UBGP|-+@3H zm%0!1Z&#AKy|w#FHUi-qkhX^PvDRD5XId}!ACm_>6SwC6MhTv{9UAihCU-OtV^;O- P00000NkvXXu0mjfZE%Ah literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/yellow_false.png b/src/main/angular/public/webmine/block/activator/yellow_false.png new file mode 100644 index 0000000000000000000000000000000000000000..157136866b4502bf6c377d16f32bf73ecd356ea9 GIT binary patch literal 311 zcmV-70m%M|P)k7jl429z>3VX_eujqC*vPoBSF%~IQDE$+Az8;g9L`3!VEL#ZxIp-w+r4+gWuIB*G zOPbv+yd~4`A^EO0}{YqoB_O-2`a=5a3t;(W?(nKEb;3Ca3XQ$ zk68a}P}{ppx^tihfim>2VSpnDtq-`%5F0$bKm$0X6ShvA0$_20A@H7l8F5l+M{vlC?H38%n*6#uBfNWR{OaVs3!10}&ged!M?*G!Q0$`5^!R002ov JPDHLkV1jpab}Rq@ literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/yellow_null.png b/src/main/angular/public/webmine/block/activator/yellow_null.png new file mode 100644 index 0000000000000000000000000000000000000000..f0183471f3444d35650ab78b8d0f1c95e48503b3 GIT binary patch literal 373 zcmV-*0gC>KP)mO=dC@|2XLTcr693l(OzOZD;^2%Q6MP7^7~0>pJjq z`FRv-19_81sh*@*MF+5e20@Upweq3S8X=V;kWws)EfyhP=qVe*xhnwSs5Ljm8sHF) zVC<-FPGC_<9RD|uAtofU1qiY{afpdyhqXQ<0(fx}WC6Jo-e$Wht)Jpt%qv0k1Hi8g zg2cn84L|@L0!VhxGoJ{o@rwuG^D3#zA*v2k0d`Jo5DtJwIE3v1m|=iuI}=Q#Ts`y* zI5rHxbM8fQh)!{;ng0U_wG{si>&sve T9JRf!00000NkvXXu0mjfduW!` literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/activator/yellow_true.png b/src/main/angular/public/webmine/block/activator/yellow_true.png new file mode 100644 index 0000000000000000000000000000000000000000..6b42e7ca52b83bcf6de26a95799f07fabc869b8c GIT binary patch literal 316 zcmV-C0mJ@@P))uirk9lnyyK%@qQ*03?werx$k>y^H7c_1=zZ|>hH!4tQ$#{2>+aWoKYdnk1P O0000hhhZq7# z;t)XQy*Swo2_}`ZZe-0K+ge0hs4GTmbz> z;F>=r2ug_tKs7-S{Q-bb zi^AzCumIlaKD!82h<_YID(a?hvlTjisb??v#4Fldi_mD9@V)$|GmpSyskSK^77f}Dxc+!E&WnEHSj+mUaZ|>* z8Hs{(7bwqAY!G=Ts@uImD=LP23HJf{HJpMEq!w`+Xq}r|^xQUbZTeY;(!!U!ST0>z zovfdn@p8dD4$qvkA9We_C9iI&dA3da0z=$SPE(GV|F3yu)-m)hjL$fa z`b8t{Jl(mfr#WucZ8p4MHpjPbPBBCH0Z|JP2e)S{XYiJ~a0z61>4QAT;OXk;vd$@? F2>?U1Vy*xH literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/cyan_false.png b/src/main/angular/public/webmine/block/observer/cyan_false.png new file mode 100644 index 0000000000000000000000000000000000000000..32db097efd692b9f962e428990479d5803aa387e GIT binary patch literal 266 zcmV+l0rmcgP)Eft&*1hjrj4+<3gR*5V|@6hIS4 zrpR21lih&Ot_iUmVi`b+Q`Q_vgD?SBvL|uMo*-6SI5-dvfNZP>DD6hf*RH?<_{k%* zAgKOtD?@rzp^b?5`><9#8#*Dw$TR(}?r}}StA~5y5+EH&2R;sXO6IOTPb5kT5W(n` Q0000007*qoM6N<$g31(bZvX%Q literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/cyan_null.png b/src/main/angular/public/webmine/block/observer/cyan_null.png new file mode 100644 index 0000000000000000000000000000000000000000..43e7a1bebfbacc432c4ad926d7d004eb321c190c GIT binary patch literal 343 zcmV-d0jU0oP)wJA0rZ=J zcdE~Ar{1X9s`daBXl;eiY^hDS2ZUwA+y#~aZSQ(?O#nFHhS0H|#V4CqLKgspvQar1 zgEKCec+-ZQ3V@GT6FFpt)1M*o-y~PaK%e9!kSf3&wG=6H@F>1N1?Rk604X6vfU0w{ zCO}xu7u6i%ZvaGbPmyO1S(wPmE6Ir2`<8=e;RnEqo&eypgrJgW0Mru%(H{T^wJaQ7 z0t;X+_t8zLLY%P;si~U|B0fNxjbegmdKh_*)Y|Fj(e3v_;-UB43m~)UO1AYr$oojL p<+_k~BV8kR0rZoBHTBy|=L18Imc^)Of{7<73|m>Klv4LM>>&nNY7E0lR1zQ+NCp0-0M9M* zpuPh6fG%1=!y*MbgP8(I;ZadtgjKQPdLTmp{@_iB0JjibfLU?wlsg~E>OJcQXp2kN z8pu+wjXjN_r-ODPB(XQgw*dH#y%O@7+pfR@c;zRwAY}e~BW3vkG=^fe$Xsh%_WFi2 zYA&NNEAB0wkY$8yzpF9OHoTe~IhO#bKq_!3fQ-yjF&jiJ3J{r8b=Uv^002ovPDHLk FV1j`oWSIZ} literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/green_false.png b/src/main/angular/public/webmine/block/observer/green_false.png new file mode 100644 index 0000000000000000000000000000000000000000..fd80426b32a3f5e76c8c32dc62698e1ae320ceab GIT binary patch literal 270 zcmV+p0rCEcP)y9-@AD-pl~Vfs30nk!T5AYEYpo$o$y~MP2D$qR U5aV%Pvj6}907*qoM6N<$f~v7;djJ3c literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/green_null.png b/src/main/angular/public/webmine/block/observer/green_null.png new file mode 100644 index 0000000000000000000000000000000000000000..49f83d1e538a18da0e465c0004b6b883a8db487b GIT binary patch literal 347 zcmV-h0i^zkP)Jrb0P{SD3!vW& zJac_+2lYnHR<#G9Kx;3AW=n0tBOojr<}R=dXnWVAYXZOlH-wJ$EI!$^61o5&RE)}G z49>V<;z=8FDgZuWC346N=RZT_?Qf)e_=o07Nn7$P0%o56H@tGa+X0Qx2YmF90ig0)Wpo1eHVspq?Oz{s2Iz zW#RM^SOD*GpWTEi#6PwnHFeWL#3xL%QA`j`40c3Vv$@bm{ tc^^r(To)2=q+8@JfPONtrG7{0yZ{+@KoCZ(&@KP~002ovPDHLkV1jY0g^U0I literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/green_true.png b/src/main/angular/public/webmine/block/observer/green_true.png new file mode 100644 index 0000000000000000000000000000000000000000..180775bc2d34c81c8c0a44b9fc99efc2de24729a GIT binary patch literal 253 zcmV#W_x^#fW9#+p?gx-|c+HbC*O@-`rsQ~L6_T0l` zG|h@@M`y}0LayJ_9OxQ8ZI09hflwe6I2Axv=B=C!bwmmf^ZO?P00000NkvXXu0mjf D7w%>c literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/magenta_false.png b/src/main/angular/public/webmine/block/observer/magenta_false.png new file mode 100644 index 0000000000000000000000000000000000000000..6d8a8362b17992b8488cd314d5fafb6c33fa7b9e GIT binary patch literal 267 zcmV+m0rdWfP)2?4B002ovPDHLkV1jnuYxDpB literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/magenta_null.png b/src/main/angular/public/webmine/block/observer/magenta_null.png new file mode 100644 index 0000000000000000000000000000000000000000..cdac118eeeb3f3fee0614e4fa2760c2f660ff241 GIT binary patch literal 345 zcmV-f0jBA)Qgo^bn-r2Mix&Ux!)hd(G zIOBqeZ(5U60kDac$RJak{|u4;rgOy^$kRCqqzW*3EqTffJc~%YSb_vufRqp-K-D-U zUxF}PaB7U=)wPJ#3`WShh)Y|Llk?nUN@z8tbf^wcHX9F_=d*?x3 rN0KhrK;rdu%efn%p9pNp-%&Cj>1a3*`Dhl100000NkvXXu0mjf^P7sF literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/magenta_true.png b/src/main/angular/public/webmine/block/observer/magenta_true.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d3a79f472e031b47960f88a5e1816640bd4f02 GIT binary patch literal 270 zcmV+p0rCEcP);v_mm#8E+)8GOBsxAV?qS}WGDh<>0Wm?3~9wvy^3tctg<2Vw&7k7z&yxP|BfjFNMuT)8D{^lTWQ zO}Zrz;Q#;t literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/red_false.png b/src/main/angular/public/webmine/block/observer/red_false.png new file mode 100644 index 0000000000000000000000000000000000000000..93c5c6bc5976f737f4f34be3d074727a5fa1a909 GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJE1oWnArbCxrzUbW8}PK=))uo_ zdO^F)&RwO<)H79&C+I`_jbHorn0WeWZhc!AEy=J{M7$v?o2#KfLFR`(d%*T-sc*HH z*&T3=-fVhyPU4$tPXi?e&iKwEL5|GdZVJ;Gek}OALHD$#bY<-K@|Q9EhAb0~C^np^ zE>vP{nW8r5sla81y*~s^i=CSS7}FN17k15ynOXSoFk?A`%i>svdH>fhcVPZ;QuT_$ zihuIj&bva^^>&m?Y!9=MveG+z!sze2cZR-)ciV5D!wqtfh0KO2XM(HacK+rPkoglP Q2lO9mdKI;Vst01MM+q5uE@ literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/red_null.png b/src/main/angular/public/webmine/block/observer/red_null.png new file mode 100644 index 0000000000000000000000000000000000000000..bb81d2e905ad29a48f59c74d29d1650583d3c6ec GIT binary patch literal 343 zcmV-d0jU0oP)g=Ls$(Su(YUuLlY-gpl&D+KK?^x~>YqIF8`~Xg34z zT%XrTy-~7N>;WjyItroLQk(D$2+M}K3oHZL-u3930C2z!p<_LZPd2TDE&vFvMrASv zXIwDxPaAS706txwlmm%_JlH15YpX4NvBES^26e)Mm_v>m5!}2WX0%Qvz0u-H- zc?rUBzNoGt{sur436XP$JP-L>%-_W9&E?=(_yMq@{{q127Gf@m20%SQ5bXhgP|L#Q zC9nXda$ntqD#RVzkea&cAmTf#+9)Q7s)v#1NG+X?9^G~?Bp!Oty#O+Yu4G5=gS?L< pTdoUPaq7DyLBU$6-gU=Ps*7!}t}wWFS_ zY%C4niyvK^Wp<^Wg|7gZ4*W!v#B7d}53s>@Bp(6TzVXEm=q2={#>Q7p$f)2=iH-fu zF)UpnbA9>;o-`txc$yWrmd=!AglxaNcc5)}H#rg)0HHuAa43L`%v*Opbwmmf(IjPY P00000NkvXXu0mjfPo!u# literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/yellow_false.png b/src/main/angular/public/webmine/block/observer/yellow_false.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9e07b56c1908f9c4b70dd75dfb56a670368228 GIT binary patch literal 269 zcmV+o0rLKdP)=t`bRZr0 zF9%S!p5^T;u09||d-pX>A{hHz8jK3S!h51p1VYxn8OSaGK3G5O{qmL#qU+;bJ2yg1 z0W@(4Ao5Y1NdTdx31=n5GQcRlMZLM9L6`t5(X%*5&#tVv@Zmr>0HU!jpv?c*Q(yu7 zqz)Az!M{MkG5-SSy|losePVnSMw2xTfLJpL@dGu_btqhn#Kdhqd_7QzA%v8F-Btv^G)-Lq=6Mb$z_=N> z=lWb4^+wHBwFjU;>nMa~OKrjy5S9&d7gz?gz3b660pNfeLdSX*pKMwQT>ua&MrASv zXWTIHPaAS706txwlmm%_JlH18ZpX4NvD!>x86lvk$T2>?8wr4>XAbSWApz55= zOAvk&n7@hHTg$<-@CIN-{{mQw%Tm5H%d;dJ0QCexj0XTh zEek&{fd#PUn05QQ6IwQ*3h|F^Na1xIM0{s;8^r|C^)T`rskPJ5qdV?}#6$177eMCF xmF(z!koS>f%XJ~~M*5B12{29uzNud;od@Y?I1pzyny>%>002ovPDHLkV1mEplSKdk literal 0 HcmV?d00001 diff --git a/src/main/angular/public/webmine/block/observer/yellow_true.png b/src/main/angular/public/webmine/block/observer/yellow_true.png new file mode 100644 index 0000000000000000000000000000000000000000..b4581dfa51f48defcb1132de89330dbf9c1978f2 GIT binary patch literal 266 zcmV+l0rmcgP)6XyMUl#Z{>>*b6ox0vsW_0JGxOsdY4x zm5rqVV)4D}eVKhy&%>Vpm=5AZw8U(VgAcI5ek5)IsNZ=014aqssIl=UCuCHJrohJj z<`}lFkhwno12-CxPrS^Ed#1l+8A03U;w5MsUd#=JB|s{W3cMA7X68$mUPLVl5Xc`i Qg8%>k07*qoM6N<$f<4G@UjP6A literal 0 HcmV?d00001 diff --git a/src/main/angular/src/app/app.routes.ts b/src/main/angular/src/app/app.routes.ts index 7b8a2e9..e05f6dc 100644 --- a/src/main/angular/src/app/app.routes.ts +++ b/src/main/angular/src/app/app.routes.ts @@ -1,5 +1,5 @@ import {Routes} from '@angular/router'; -import {ServerListComponent} from './server-list/server-list.component'; +import {ServerListComponent} from './server/list/server-list.component'; export const routes: Routes = [ {path: '**', component: ServerListComponent}, diff --git a/src/main/angular/src/app/crud/CrudHelpers.ts b/src/main/angular/src/app/crud/CrudHelpers.ts index 9835a6f..ef4e18d 100644 --- a/src/main/angular/src/app/crud/CrudHelpers.ts +++ b/src/main/angular/src/app/crud/CrudHelpers.ts @@ -2,39 +2,61 @@ export type FromJson = (json: any) => T; export type Next = (t: T) => any; -export function validateString(json: any) { - if (typeof json === 'string') { - return json; - } - throw new Error('Not a string: ' + JSON.stringify(json)); -} - export function orNull(t: T | null, map: (t: T) => R): R | null { - if (t === null) { + if (t === null || t === undefined) { return null; } return map(t); } -export function orElse(t: T | null, map: (t: T) => R, orElse: R): R { - if (t === null) { +export function orElse(t: T | null | undefined, map: (t: T) => R, orElse: R): R { + if (t === null || t === undefined) { return orElse; } return map(t); } export function validateBoolean(json: any) { - if (typeof json === 'boolean') { - return json; + if (typeof json !== 'boolean') { + throw new Error(`Not a Boolean: type=${typeof json}, value=${JSON.stringify(json)}`); } - throw new Error('Not a boolean: ' + JSON.stringify(json)); + return json; } export function validateNumber(json: any) { - if (typeof json === 'number') { - return json; + if (typeof json !== 'number') { + throw new Error(`Not a Number: type=${typeof json}, value=${JSON.stringify(json)}`); } - throw new Error('Not a number: ' + JSON.stringify(json)); + return json; +} + +export function validateString(json: any) { + if (typeof json !== 'string') { + throw new Error(`Not a String: type=${typeof json}, value=${JSON.stringify(json)}`); + } + return json; +} + +export function validateList(json: any, fromJson: FromJson): T[] { + if (!Array.isArray(json)) { + throw new Error(`Not a list: type=${typeof json}, value=${JSON.stringify(json)}`); + } + return json.map(fromJson); +} + +export function validateMap(json: any, key: FromJson, value: FromJson): Map { + if (typeof json !== 'object') { + throw new Error(`Not a Map: type=${typeof json}, value=${JSON.stringify(json)}`); + } + + const result = new Map(); + for (const [rawKey, rawValue] of Object.entries(json)) { + const parsedKey = key(rawKey); + const parsedValue = value(rawValue); + result.set(parsedKey, parsedValue); + } + + return result; } export function url(protocol: string, path: any[]): string { diff --git a/src/main/angular/src/app/server-list/Server.ts b/src/main/angular/src/app/server-list/Server.ts deleted file mode 100644 index 3b91d8a..0000000 --- a/src/main/angular/src/app/server-list/Server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {Mode} from "./Mode"; -import {validateBoolean, validateNumber, validateString} from "../crud/CrudHelpers"; - -export class Server { - - constructor( - readonly name: string, - readonly motd: string, - readonly mode: Mode, - readonly port: number, - readonly running: boolean, - readonly icon: boolean, - ) { - // - } - - static fromJson(json: any): Server { - return new Server( - validateString(json.name), - validateString(json.motd), - validateString(json.mode) as Mode, - validateNumber(json.port), - validateBoolean(json.running), - validateBoolean(json.icon), - ); - } - -} diff --git a/src/main/angular/src/app/server-list/server-list.component.html b/src/main/angular/src/app/server-list/server-list.component.html deleted file mode 100644 index d0c1a48..0000000 --- a/src/main/angular/src/app/server-list/server-list.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
- Minecraft -
- -
-
-
- {{server.mode}} - {{server.mode}} -
-
- {{ server.motd }} -
- 10.255.0.1:{{ server.port }} -
-
-
- - Karte - -
-
-  An  -
-
- Aus -
-
-
- -
- (i) - Beim Starten eines Servers werden alle anderen gestoppt. -
diff --git a/src/main/angular/src/app/server-list/Mode.ts b/src/main/angular/src/app/server/Mode.ts similarity index 100% rename from src/main/angular/src/app/server-list/Mode.ts rename to src/main/angular/src/app/server/Mode.ts diff --git a/src/main/angular/src/app/server/Server.ts b/src/main/angular/src/app/server/Server.ts new file mode 100644 index 0000000..644e8fc --- /dev/null +++ b/src/main/angular/src/app/server/Server.ts @@ -0,0 +1,73 @@ +import {Mode} from "./Mode"; +import {orElse, validateBoolean, validateList, validateNumber, validateString} from "../crud/CrudHelpers"; + +export enum Channel { + red = 'red', + blue = 'blue', + green = 'green', + yellow = 'yellow', + magenta = 'magenta', + cyan = 'cyan', +} + +export function validateChannel(json: any): Channel { + return validateString(json) as Channel; +} + +export class ChannelInstance { + + constructor( + readonly channel: Channel, + readonly signal: number, + ) { + // + } + + static fromJson(json: any): ChannelInstance { + return new ChannelInstance( + validateChannel(json.channel), + validateNumber(json.signal), + ); + } + +} + +export class Server { + + constructor( + readonly name: string, + readonly motd: string, + readonly mode: Mode, + readonly port: number, + readonly running: boolean, + readonly webmine: boolean, + readonly icon: boolean, + readonly activators: ChannelInstance[], + readonly observers: ChannelInstance[], + ) { + // + } + + static fromJson(json: any): Server { + return new Server( + validateString(json.name), + validateString(json.motd), + validateString(json.mode) as Mode, + validateNumber(json.port), + validateBoolean(json.running), + validateBoolean(json.webmine), + validateBoolean(json.icon), + validateList(json['activators'], ChannelInstance.fromJson), + validateList(json['observers'], ChannelInstance.fromJson), + ); + } + + isActivatorPowered(channel: Channel): boolean | null { + return orElse(this.activators.filter(c => c.channel === channel).map(c => c.signal > 0)[0], v => v, null); + } + + isObserverPowered(channel: Channel): boolean | null { + return orElse(this.observers.filter(c => c.channel === channel).map(c => c.signal > 0)[0], v => v, null); + } + +} diff --git a/src/main/angular/src/app/server/list/server-list.component.html b/src/main/angular/src/app/server/list/server-list.component.html new file mode 100644 index 0000000..5681e40 --- /dev/null +++ b/src/main/angular/src/app/server/list/server-list.component.html @@ -0,0 +1,52 @@ +
+ Minecraft +
+ +
+
+
+ {{server.mode}} + {{server.mode}} +
+
+ {{ server.motd }} +
+ 10.255.0.1:{{ server.port }} +
+
+
+ + Karte + +
+
+  An  +
+
+ Aus +
+ + +
+
+ {{channel}} + {{channel}} + {{channel}} +
+
+
+
+ {{channel}} + {{channel}} + {{channel}} +
+
+
+ +
+
+ +
+ (x) + Nicht verbunden! +
diff --git a/src/main/angular/src/app/server-list/server-list.component.less b/src/main/angular/src/app/server/list/server-list.component.less similarity index 68% rename from src/main/angular/src/app/server-list/server-list.component.less rename to src/main/angular/src/app/server/list/server-list.component.less index d0fe915..28283f4 100644 --- a/src/main/angular/src/app/server-list/server-list.component.less +++ b/src/main/angular/src/app/server/list/server-list.component.less @@ -3,16 +3,22 @@ .server { display: flex; + flex-wrap: wrap; flex-direction: row; align-items: center; + border-bottom: 1px solid #464d55; > div { padding: 0.25em; } .icon { + height: 2em; + width: 2em; + text-align: center; + img { - height: 2em; + height: 100%; vertical-align: middle; } } @@ -25,6 +31,7 @@ .map { font-weight: bold; + img { height: 2em; vertical-align: middle; @@ -61,6 +68,23 @@ color: white; } + .channelInstanceList { + width: 100%; + display: flex; + flex-direction: row; + + .channelInstance { + display: block; + width: calc(100% / 6); + padding-left: 0.25em; + padding-right: 0.25em; + + img { + width: 100%; + } + } + } + } } diff --git a/src/main/angular/src/app/server-list/server-list.component.ts b/src/main/angular/src/app/server/list/server-list.component.ts similarity index 68% rename from src/main/angular/src/app/server-list/server-list.component.ts rename to src/main/angular/src/app/server/list/server-list.component.ts index 1712474..d0d271a 100644 --- a/src/main/angular/src/app/server-list/server-list.component.ts +++ b/src/main/angular/src/app/server/list/server-list.component.ts @@ -1,9 +1,9 @@ import {Component} from '@angular/core'; import {NgForOf, NgIf} from '@angular/common'; -import {Server} from './Server'; -import {CrudListComponent} from '../crud/CrudListComponent'; -import {ServerService} from './server.service'; -import {url} from '../crud/CrudHelpers'; +import {Channel, Server} from '../Server'; +import {CrudListComponent} from '../../crud/CrudListComponent'; +import {ServerService} from '../server.service'; +import {url} from '../../crud/CrudHelpers'; @Component({ selector: 'app-server-list', @@ -36,4 +36,8 @@ export class ServerListComponent extends CrudListComponent a.port - b.port); } + channels(): Channel[] { + return Object.keys(Channel).filter(key => isNaN(Number(key))).map(key => key as Channel); + } + } diff --git a/src/main/angular/src/app/server-list/server.service.ts b/src/main/angular/src/app/server/server.service.ts similarity index 72% rename from src/main/angular/src/app/server-list/server.service.ts rename to src/main/angular/src/app/server/server.service.ts index b9927d3..8d14469 100644 --- a/src/main/angular/src/app/server-list/server.service.ts +++ b/src/main/angular/src/app/server/server.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {CrudService} from '../crud/CrudService'; -import {Server} from './Server'; +import {Channel, Server} from './Server'; import {ApiService} from '../crud/ApiService'; import {Next} from '../crud/CrudHelpers'; @@ -23,4 +23,8 @@ export class ServerService extends CrudService { this.getSingle([server.name, 'stop'], next); } + webmineActivatorToggle(server: Server, channel: Channel) { + this.getNone(['Activator', server.name, channel, server.isActivatorPowered(channel) === true ? 0 : 15]) + } + } diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index 36e18d7..53a3c56 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -33,3 +33,7 @@ body { background-color: #fdaaaa; border: 0.1em solid red; } + +.hidden { + display: none; +} diff --git a/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java b/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java new file mode 100644 index 0000000..63fecaf --- /dev/null +++ b/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java @@ -0,0 +1,22 @@ +package de.ph87.mc.server; + +import de.ph87.mc.webmine.Channel; +import lombok.Data; +import lombok.NonNull; + +import java.util.Map; + +@Data +public class ChannelInstanceDto { + + @NonNull + public final Channel channel; + + public final int signal; + + public ChannelInstanceDto(@NonNull final Map.Entry entry) { + this.channel = entry.getKey(); + this.signal = entry.getValue(); + } + +} diff --git a/src/main/java/de/ph87/mc/server/EConsumer.java b/src/main/java/de/ph87/mc/server/EConsumer.java new file mode 100644 index 0000000..3e6b3d1 --- /dev/null +++ b/src/main/java/de/ph87/mc/server/EConsumer.java @@ -0,0 +1,8 @@ +package de.ph87.mc.server; + +@FunctionalInterface +public interface EConsumer { + + void accept(final T t) throws E; + +} diff --git a/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java b/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java new file mode 100644 index 0000000..ba2108b --- /dev/null +++ b/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java @@ -0,0 +1,12 @@ +package de.ph87.mc.server; + +import de.ph87.mc.webmine.Channel; +import lombok.NonNull; + +public class InvalidChannelSignal extends Exception { + + public InvalidChannelSignal(@NonNull final Server server, @NonNull final Channel channel, final int signal) { + super("Invalid signal received: server=%s, channel=%s, signal=%s".formatted(server.properties.name, channel, signal)); + } + +} diff --git a/src/main/java/de/ph87/mc/server/Properties.java b/src/main/java/de/ph87/mc/server/Properties.java index fcdc4a1..86e3edb 100644 --- a/src/main/java/de/ph87/mc/server/Properties.java +++ b/src/main/java/de/ph87/mc/server/Properties.java @@ -31,7 +31,7 @@ public class Properties { final java.util.Properties properties = new java.util.Properties(); try (final FileReader reader = new FileReader(file)) { properties.load(reader); - name = properties.getProperty("level-name"); + name = directory.getName(); motd = properties.getProperty("motd"); mode = Mode.valueOf(properties.getProperty("gamemode").toUpperCase(Locale.ROOT)); port = Integer.parseInt(properties.getProperty("server-port")); diff --git a/src/main/java/de/ph87/mc/server/Server.java b/src/main/java/de/ph87/mc/server/Server.java index d36783e..8a6b1cc 100644 --- a/src/main/java/de/ph87/mc/server/Server.java +++ b/src/main/java/de/ph87/mc/server/Server.java @@ -1,11 +1,17 @@ package de.ph87.mc.server; +import de.ph87.mc.webmine.Channel; import jakarta.annotation.Nullable; import lombok.Data; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; @Data @Slf4j @@ -30,6 +36,12 @@ public class Server { public boolean shutdown = false; + public boolean webmine = false; + + private final Map activators = new HashMap<>(); + + private final Map observers = new HashMap<>(); + public Server(@NonNull final File directory) throws NoMinecraftServer { this.directory = directory; this.pidFile = new File(directory, "pid"); @@ -37,19 +49,47 @@ public class Server { this.properties = new Properties(directory); } - public boolean isRunning() { - synchronized (lock) { - return process != null && process.isAlive(); - } - } - @Override public String toString() { - return "Server(%s, \"%s\", %s)".formatted(properties.mode, properties.motd, isRunning() ? "RUNNING" : "stopped"); + return "Server(%s, \"%s\", %s)".formatted(properties.mode, properties.motd, process != null ? "RUNNING" : "stopped"); } public boolean eq(@NonNull final Server other) { return properties.name.equals(other.properties.name); } + public void setActivator(final Channel channel, final int signal) throws InvalidChannelSignal { + if (signal < 0 || signal > 15) { + throw new InvalidChannelSignal(this, channel, signal); + } + synchronized (activators) { + activators.put(channel, signal); + publish(channel, signal); + log.info("Activator changed: name={}, channel={}, signal={}", properties.name, channel, signal); + } + } + + public void setObserver(final Channel channel, final int signal) throws InvalidChannelSignal { + if (signal < 0 || signal > 15) { + throw new InvalidChannelSignal(this, channel, signal); + } + synchronized (observers) { + observers.put(channel, signal); + log.info("Observer changed: name={}: {}={}", properties.name, channel, signal); + } + } + + private void publish(@NonNull final Channel channel, final int signal) { + new Thread(() -> { + try { + final URLConnection con = URI.create("http://localhost:8123/set?channel=%s&signal=%d".formatted(channel, signal)).toURL().openConnection(); + con.setConnectTimeout(500); + con.setReadTimeout(500); + con.connect(); + } catch (IOException e) { + log.error("Failed to publish Activator to Server: name={}, channel={}, signal={}, error={}", properties.name, channel, signal, e.getMessage()); + } + }).start(); + } + } diff --git a/src/main/java/de/ph87/mc/server/ServerDto.java b/src/main/java/de/ph87/mc/server/ServerDto.java index 58c10c0..9b752e2 100644 --- a/src/main/java/de/ph87/mc/server/ServerDto.java +++ b/src/main/java/de/ph87/mc/server/ServerDto.java @@ -4,28 +4,44 @@ import de.ph87.mc.websocket.IWebsocketMessage; import lombok.Data; import lombok.NonNull; +import java.util.List; + @Data public class ServerDto implements IWebsocketMessage { + @NonNull public final String name; + @NonNull public final String motd; + @NonNull public final Mode mode; public final int port; public final boolean running; + public final boolean webmine; + public boolean icon; - public ServerDto(final @NonNull Server server) { + @NonNull + public final List activators; + + @NonNull + public final List observers; + + public ServerDto(@NonNull final Server server) { this.name = server.properties.name; this.motd = server.properties.motd; this.mode = server.properties.mode; this.port = server.properties.port; - this.running = server.isRunning(); + this.running = server.process != null; + this.webmine = server.webmine; this.icon = server.iconFile.isFile(); + this.activators = server.getActivators().entrySet().stream().map(ChannelInstanceDto::new).toList(); + this.observers = server.getObservers().entrySet().stream().map(ChannelInstanceDto::new).toList(); } } diff --git a/src/main/java/de/ph87/mc/server/ServerService.java b/src/main/java/de/ph87/mc/server/ServerService.java index 48549f5..3ca98a0 100644 --- a/src/main/java/de/ph87/mc/server/ServerService.java +++ b/src/main/java/de/ph87/mc/server/ServerService.java @@ -1,10 +1,13 @@ package de.ph87.mc.server; +import de.ph87.mc.webmine.Channel; import jakarta.annotation.PostConstruct; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.File; @@ -14,10 +17,12 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @Slf4j @Service +@EnableScheduling @RequiredArgsConstructor public class ServerService { @@ -37,6 +42,21 @@ public class ServerService { } } + @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) + public void schedule() { + synchronized (serversLock) { + servers.forEach(this::refresh); + } + } + + private void refresh(@NonNull final Server server) { + if (server.process != null && !server.process.isAlive()) { + log.error("Server crash detected: name={}", server.properties.name); + server.process = null; + publish(server); + } + } + @NonNull private Optional _tryLoadingFromDir(@NonNull final File directory) { try { @@ -61,13 +81,14 @@ public class ServerService { private void start(@NonNull final Server server) { synchronized (server.lock) { - if (server.isRunning()) { + if (server.process != null) { log.warn("Server is already running: name={}", server.properties.name); return; } stopAll(); + waitForAllToBeStopped(); log.info("Starting server: name={}", server.properties.name); - final ProcessBuilder builder = new ProcessBuilder("java", "-jar", "server.jar"); + final ProcessBuilder builder = new ProcessBuilder("java", "-Xms2G", "-Xmx2G", "-jar", "server.jar", "nogui"); builder.directory(server.directory); try { server.process = builder.start(); @@ -78,9 +99,21 @@ public class ServerService { } } + private void waitForAllToBeStopped() { + synchronized (serversLock) { + try { + while (servers.stream().anyMatch(server -> server.process != null)) { + serversLock.wait(1000); + } + } catch (InterruptedException e) { + log.error("Interrupted while waiting for ALL servers to stop"); + } + } + } + private void stop(@NonNull final Server server) { synchronized (server.lock) { - if (!server.isRunning()) { + if (server.process == null) { log.warn("Server is not running: name={}", server.properties.name); return; } @@ -121,6 +154,14 @@ public class ServerService { return publish(server); } + @NonNull + @SuppressWarnings("UnusedReturnValue") + private ServerDto setE(final @NonNull String name, @NonNull final EConsumer modifier) throws E { + final Server server = getByName(name); + modifier.accept(server); + return publish(server); + } + @NonNull private ServerDto publish(@NonNull final Server server) { final ServerDto dto = new ServerDto(server); @@ -131,7 +172,7 @@ public class ServerService { @NonNull private Server getByName(@NonNull final String name) { synchronized (serversLock) { - return servers.stream().filter(server -> server.properties.name.equals(name)).findFirst().orElseThrow(); + return servers.stream().filter(server -> server.properties.name.equals(name)).peek(this::refresh).findFirst().orElseThrow(); } } @@ -156,4 +197,12 @@ public class ServerService { } } + public void setActivator(final @NonNull String name, final Channel channel, final int signal) throws InvalidChannelSignal { + setE(name, server -> server.setActivator(channel, signal)); + } + + public void setObserver(final @NonNull String name, final Channel channel, final int signal) throws InvalidChannelSignal { + setE(name, server -> server.setObserver(channel, signal)); + } + } diff --git a/src/main/java/de/ph87/mc/webmine/Channel.java b/src/main/java/de/ph87/mc/webmine/Channel.java new file mode 100644 index 0000000..ba488b3 --- /dev/null +++ b/src/main/java/de/ph87/mc/webmine/Channel.java @@ -0,0 +1,5 @@ +package de.ph87.mc.webmine; + +public enum Channel { + red, blue, green, yellow, magenta, cyan, +} diff --git a/src/main/java/de/ph87/mc/webmine/WebmineController.java b/src/main/java/de/ph87/mc/webmine/WebmineController.java new file mode 100644 index 0000000..3535d08 --- /dev/null +++ b/src/main/java/de/ph87/mc/webmine/WebmineController.java @@ -0,0 +1,57 @@ +package de.ph87.mc.webmine; + +import de.ph87.mc.server.InvalidChannelSignal; +import de.ph87.mc.server.ServerService; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Arrays; + +@Slf4j +@CrossOrigin +@RestController +@RequestMapping("Server") +public class WebmineController { + + private final ServerService serverService; + + public WebmineController(final ServerService serverService) { + this.serverService = serverService; + } + + @GetMapping("Activator/{serverName}/{channelName}/{signal}") + public void activator(@NonNull @PathVariable final String serverName, @NonNull @PathVariable String channelName, @PathVariable int signal) { + final Channel channel = Arrays.stream(Channel.values()).filter(c -> c.name().equals(channelName)).findFirst().orElseThrow(() -> { + final String message = "No such channel: %s".formatted(channelName); + log.error(message); + return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); + }); + try { + serverService.setActivator(serverName, channel, signal); + } catch (InvalidChannelSignal e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + @GetMapping("Observer/{serverName}/{channelName}/{signal}") + public void observer(@NonNull @PathVariable final String serverName, @NonNull @PathVariable String channelName, @PathVariable int signal) { + final Channel channel = Arrays.stream(Channel.values()).filter(c -> c.name().equals(channelName)).findFirst().orElseThrow(() -> { + final String message = "No such channel: %s".formatted(channelName); + log.error(message); + return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); + }); + try { + serverService.setObserver(serverName, channel, signal); + } catch (InvalidChannelSignal e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + +}