add web terminal emulator (closes #6)
This commit is contained in:
		
							parent
							
								
									62c40358a2
								
							
						
					
					
						commit
						ac709e66f5
					
				
					 15 changed files with 586 additions and 87 deletions
				
			
		
							
								
								
									
										160
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										160
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
					@ -76,7 +76,7 @@ version = "1.0.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
 | 
					checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
 | 
					checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "anstyle",
 | 
					 "anstyle",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -250,7 +250,7 @@ dependencies = [
 | 
				
			||||||
 "js-sys",
 | 
					 "js-sys",
 | 
				
			||||||
 "num-traits",
 | 
					 "num-traits",
 | 
				
			||||||
 "wasm-bindgen",
 | 
					 "wasm-bindgen",
 | 
				
			||||||
 "windows-targets",
 | 
					 "windows-targets 0.48.5",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -334,12 +334,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "errno"
 | 
					name = "errno"
 | 
				
			||||||
version = "0.3.7"
 | 
					version = "0.3.8"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
 | 
					checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.52.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -483,7 +483,7 @@ version = "0.5.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
 | 
					checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -653,9 +653,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "libc"
 | 
					name = "libc"
 | 
				
			||||||
version = "0.2.150"
 | 
					version = "0.2.151"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 | 
					checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "linux-raw-sys"
 | 
					name = "linux-raw-sys"
 | 
				
			||||||
| 
						 | 
					@ -663,6 +663,16 @@ version = "0.4.11"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 | 
					checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "lock_api"
 | 
				
			||||||
 | 
					version = "0.4.11"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "autocfg",
 | 
				
			||||||
 | 
					 "scopeguard",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "log"
 | 
					name = "log"
 | 
				
			||||||
version = "0.4.20"
 | 
					version = "0.4.20"
 | 
				
			||||||
| 
						 | 
					@ -704,7 +714,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "wasi",
 | 
					 "wasi",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -772,6 +782,29 @@ version = "0.1.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 | 
					checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "parking_lot"
 | 
				
			||||||
 | 
					version = "0.12.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "lock_api",
 | 
				
			||||||
 | 
					 "parking_lot_core",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "parking_lot_core"
 | 
				
			||||||
 | 
					version = "0.9.9"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "libc",
 | 
				
			||||||
 | 
					 "redox_syscall",
 | 
				
			||||||
 | 
					 "smallvec",
 | 
				
			||||||
 | 
					 "windows-targets 0.48.5",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "percent-encoding"
 | 
					name = "percent-encoding"
 | 
				
			||||||
version = "2.3.0"
 | 
					version = "2.3.0"
 | 
				
			||||||
| 
						 | 
					@ -912,6 +945,7 @@ dependencies = [
 | 
				
			||||||
 "prost",
 | 
					 "prost",
 | 
				
			||||||
 "regex",
 | 
					 "regex",
 | 
				
			||||||
 "reqwest",
 | 
					 "reqwest",
 | 
				
			||||||
 | 
					 "rustix",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "sysinfo",
 | 
					 "sysinfo",
 | 
				
			||||||
| 
						 | 
					@ -1045,15 +1079,16 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "rustix"
 | 
					name = "rustix"
 | 
				
			||||||
version = "0.38.24"
 | 
					version = "0.38.28"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234"
 | 
					checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "bitflags 2.4.1",
 | 
					 "bitflags 2.4.1",
 | 
				
			||||||
 "errno",
 | 
					 "errno",
 | 
				
			||||||
 | 
					 "itoa",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "linux-raw-sys",
 | 
					 "linux-raw-sys",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.52.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1068,6 +1103,12 @@ version = "1.0.15"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 | 
					checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "scopeguard"
 | 
				
			||||||
 | 
					version = "1.2.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde"
 | 
					name = "serde"
 | 
				
			||||||
version = "1.0.192"
 | 
					version = "1.0.192"
 | 
				
			||||||
| 
						 | 
					@ -1161,7 +1202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
 | 
					checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1232,7 +1273,7 @@ dependencies = [
 | 
				
			||||||
 "fastrand",
 | 
					 "fastrand",
 | 
				
			||||||
 "redox_syscall",
 | 
					 "redox_syscall",
 | 
				
			||||||
 "rustix",
 | 
					 "rustix",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1271,11 +1312,12 @@ dependencies = [
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "mio",
 | 
					 "mio",
 | 
				
			||||||
 "num_cpus",
 | 
					 "num_cpus",
 | 
				
			||||||
 | 
					 "parking_lot",
 | 
				
			||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
 "signal-hook-registry",
 | 
					 "signal-hook-registry",
 | 
				
			||||||
 "socket2 0.5.5",
 | 
					 "socket2 0.5.5",
 | 
				
			||||||
 "tokio-macros",
 | 
					 "tokio-macros",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1654,7 +1696,7 @@ version = "0.51.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
 | 
					checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "windows-targets",
 | 
					 "windows-targets 0.48.5",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1663,7 +1705,16 @@ version = "0.48.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 | 
					checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "windows-targets",
 | 
					 "windows-targets 0.48.5",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows-sys"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "windows-targets 0.52.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1672,13 +1723,28 @@ version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 | 
					checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "windows_aarch64_gnullvm",
 | 
					 "windows_aarch64_gnullvm 0.48.5",
 | 
				
			||||||
 "windows_aarch64_msvc",
 | 
					 "windows_aarch64_msvc 0.48.5",
 | 
				
			||||||
 "windows_i686_gnu",
 | 
					 "windows_i686_gnu 0.48.5",
 | 
				
			||||||
 "windows_i686_msvc",
 | 
					 "windows_i686_msvc 0.48.5",
 | 
				
			||||||
 "windows_x86_64_gnu",
 | 
					 "windows_x86_64_gnu 0.48.5",
 | 
				
			||||||
 "windows_x86_64_gnullvm",
 | 
					 "windows_x86_64_gnullvm 0.48.5",
 | 
				
			||||||
 "windows_x86_64_msvc",
 | 
					 "windows_x86_64_msvc 0.48.5",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows-targets"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "windows_aarch64_gnullvm 0.52.0",
 | 
				
			||||||
 | 
					 "windows_aarch64_msvc 0.52.0",
 | 
				
			||||||
 | 
					 "windows_i686_gnu 0.52.0",
 | 
				
			||||||
 | 
					 "windows_i686_msvc 0.52.0",
 | 
				
			||||||
 | 
					 "windows_x86_64_gnu 0.52.0",
 | 
				
			||||||
 | 
					 "windows_x86_64_gnullvm 0.52.0",
 | 
				
			||||||
 | 
					 "windows_x86_64_msvc 0.52.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -1687,42 +1753,84 @@ version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 | 
					checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_aarch64_gnullvm"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_aarch64_msvc"
 | 
					name = "windows_aarch64_msvc"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 | 
					checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_aarch64_msvc"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_i686_gnu"
 | 
					name = "windows_i686_gnu"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 | 
					checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_i686_gnu"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_i686_msvc"
 | 
					name = "windows_i686_msvc"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 | 
					checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_i686_msvc"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_x86_64_gnu"
 | 
					name = "windows_x86_64_gnu"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 | 
					checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_gnu"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_x86_64_gnullvm"
 | 
					name = "windows_x86_64_gnullvm"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 | 
					checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_gnullvm"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_x86_64_msvc"
 | 
					name = "windows_x86_64_msvc"
 | 
				
			||||||
version = "0.48.5"
 | 
					version = "0.48.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 | 
					checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_msvc"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "winreg"
 | 
					name = "winreg"
 | 
				
			||||||
version = "0.50.0"
 | 
					version = "0.50.0"
 | 
				
			||||||
| 
						 | 
					@ -1730,5 +1838,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 | 
					checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,11 @@ once_cell = "1.18.0"
 | 
				
			||||||
prost = "0.12.1"
 | 
					prost = "0.12.1"
 | 
				
			||||||
regex = "1.10.2"
 | 
					regex = "1.10.2"
 | 
				
			||||||
reqwest = { version = "0.11.18", features = ["blocking", "json"], default-features = false }
 | 
					reqwest = { version = "0.11.18", features = ["blocking", "json"], default-features = false }
 | 
				
			||||||
 | 
					rustix = { version = "0.38.28", features = ["fs", "process", "pty", "stdio", "termios"] }
 | 
				
			||||||
serde = { version = "1.0.173", features = ["derive"] }
 | 
					serde = { version = "1.0.173", features = ["derive"] }
 | 
				
			||||||
serde_json = "1.0.103"
 | 
					serde_json = "1.0.103"
 | 
				
			||||||
sysinfo = { version = "0.29.2", default-features = false }
 | 
					sysinfo = { version = "0.29.2", default-features = false }
 | 
				
			||||||
tokio = { version = "1.28.2", features = ["rt-multi-thread", "io-util", "process", "macros", "signal"] }
 | 
					tokio = { version = "1.28.2", features = ["full"] }
 | 
				
			||||||
tokio-stream = { version = "0.1.14", features = ["net", "sync"] }
 | 
					tokio-stream = { version = "0.1.14", features = ["net", "sync"] }
 | 
				
			||||||
tokio-util = { version = "0.7.10", features = ["codec"] }
 | 
					tokio-util = { version = "0.7.10", features = ["codec"] }
 | 
				
			||||||
tonic = { version = "0.10.2" }
 | 
					tonic = { version = "0.10.2" }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ pub mod config;
 | 
				
			||||||
pub mod debian;
 | 
					pub mod debian;
 | 
				
			||||||
pub mod health;
 | 
					pub mod health;
 | 
				
			||||||
pub mod info;
 | 
					pub mod info;
 | 
				
			||||||
 | 
					pub mod pty;
 | 
				
			||||||
pub mod self_update;
 | 
					pub mod self_update;
 | 
				
			||||||
pub mod server;
 | 
					pub mod server;
 | 
				
			||||||
pub mod task;
 | 
					pub mod task;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										166
									
								
								agent/src/pty.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								agent/src/pty.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,166 @@
 | 
				
			||||||
 | 
					use std::{io, task::ready};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use rustix::{
 | 
				
			||||||
 | 
					    fd::OwnedFd,
 | 
				
			||||||
 | 
					    fs::{fcntl_getfl, fcntl_setfl, OFlags},
 | 
				
			||||||
 | 
					    process::{ioctl_tiocsctty, setsid},
 | 
				
			||||||
 | 
					    pty::{grantpt, ioctl_tiocgptpeer, openpt, unlockpt, OpenptFlags},
 | 
				
			||||||
 | 
					    stdio::{dup2_stderr, dup2_stdin, dup2_stdout},
 | 
				
			||||||
 | 
					    termios::{tcsetwinsize, Winsize},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use tokio::{
 | 
				
			||||||
 | 
					    io::{unix::AsyncFd, AsyncRead, AsyncWrite},
 | 
				
			||||||
 | 
					    process::Child,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct Pty {
 | 
				
			||||||
 | 
					    fd: AsyncFd<OwnedFd>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Pty {
 | 
				
			||||||
 | 
					    pub fn open() -> io::Result<Self> {
 | 
				
			||||||
 | 
					        let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)?;
 | 
				
			||||||
 | 
					        grantpt(&master)?;
 | 
				
			||||||
 | 
					        unlockpt(&master)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set nonblocking
 | 
				
			||||||
 | 
					        let flags = fcntl_getfl(&master)?;
 | 
				
			||||||
 | 
					        fcntl_setfl(&master, flags | OFlags::NONBLOCK)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let fd = AsyncFd::new(master)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Self { fd })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn child(&self) -> io::Result<PtyChild> {
 | 
				
			||||||
 | 
					        // NOTE: Linux v4.13 and above
 | 
				
			||||||
 | 
					        let fd = ioctl_tiocgptpeer(&self.fd, OpenptFlags::RDWR | OpenptFlags::NOCTTY)?;
 | 
				
			||||||
 | 
					        let child = PtyChild { fd };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(child)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn resize_window(&self, rows: u16, cols: u16) -> io::Result<()> {
 | 
				
			||||||
 | 
					        let winsize = Winsize {
 | 
				
			||||||
 | 
					            ws_row: rows,
 | 
				
			||||||
 | 
					            ws_col: cols,
 | 
				
			||||||
 | 
					            ws_xpixel: 0,
 | 
				
			||||||
 | 
					            ws_ypixel: 0,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tcsetwinsize(&self.fd, winsize)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn try_clone(&self) -> io::Result<Pty> {
 | 
				
			||||||
 | 
					        let fd = self.fd.get_ref().try_clone()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Pty {
 | 
				
			||||||
 | 
					            fd: AsyncFd::new(fd)?,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AsyncRead for Pty {
 | 
				
			||||||
 | 
					    fn poll_read(
 | 
				
			||||||
 | 
					        self: std::pin::Pin<&mut Self>,
 | 
				
			||||||
 | 
					        cx: &mut std::task::Context<'_>,
 | 
				
			||||||
 | 
					        buf: &mut tokio::io::ReadBuf<'_>,
 | 
				
			||||||
 | 
					    ) -> std::task::Poll<io::Result<()>> {
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            let mut guard = ready!(self.fd.poll_read_ready(cx)?);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match guard.try_io(|inner| {
 | 
				
			||||||
 | 
					                let fd = inner.get_ref();
 | 
				
			||||||
 | 
					                let n = rustix::io::read(fd, buf.initialize_unfilled())?;
 | 
				
			||||||
 | 
					                buf.advance(n);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(())
 | 
				
			||||||
 | 
					            }) {
 | 
				
			||||||
 | 
					                Ok(result) => return std::task::Poll::Ready(result),
 | 
				
			||||||
 | 
					                Err(_would_block) => continue,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AsyncWrite for Pty {
 | 
				
			||||||
 | 
					    fn poll_write(
 | 
				
			||||||
 | 
					        self: std::pin::Pin<&mut Self>,
 | 
				
			||||||
 | 
					        cx: &mut std::task::Context<'_>,
 | 
				
			||||||
 | 
					        buf: &[u8],
 | 
				
			||||||
 | 
					    ) -> std::task::Poll<Result<usize, io::Error>> {
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            let mut guard = ready!(self.fd.poll_write_ready(cx))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match guard.try_io(|inner| Ok(rustix::io::write(inner.get_ref(), buf)?)) {
 | 
				
			||||||
 | 
					                Ok(result) => return std::task::Poll::Ready(result),
 | 
				
			||||||
 | 
					                Err(_would_block) => continue,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn poll_flush(
 | 
				
			||||||
 | 
					        self: std::pin::Pin<&mut Self>,
 | 
				
			||||||
 | 
					        _cx: &mut std::task::Context<'_>,
 | 
				
			||||||
 | 
					    ) -> std::task::Poll<Result<(), io::Error>> {
 | 
				
			||||||
 | 
					        std::task::Poll::Ready(Ok(()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn poll_shutdown(
 | 
				
			||||||
 | 
					        self: std::pin::Pin<&mut Self>,
 | 
				
			||||||
 | 
					        _cx: &mut std::task::Context<'_>,
 | 
				
			||||||
 | 
					    ) -> std::task::Poll<Result<(), io::Error>> {
 | 
				
			||||||
 | 
					        std::task::Poll::Ready(Ok(()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct PtyChild {
 | 
				
			||||||
 | 
					    fd: OwnedFd,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl PtyChild {
 | 
				
			||||||
 | 
					    pub fn login_tty(&self) -> io::Result<()> {
 | 
				
			||||||
 | 
					        setsid()?;
 | 
				
			||||||
 | 
					        ioctl_tiocsctty(&self.fd)?;
 | 
				
			||||||
 | 
					        dup2_stdin(&self.fd)?;
 | 
				
			||||||
 | 
					        dup2_stdout(&self.fd)?;
 | 
				
			||||||
 | 
					        dup2_stderr(&self.fd)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn open_shell(pty_child: PtyChild) -> io::Result<Child> {
 | 
				
			||||||
 | 
					    let mut cmd = tokio::process::Command::new("/bin/bash");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    unsafe {
 | 
				
			||||||
 | 
					        cmd.pre_exec(move || {
 | 
				
			||||||
 | 
					            pty_child.login_tty()?;
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cmd.spawn()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use rustix::fd::AsRawFd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
					    async fn can_open_pty() {
 | 
				
			||||||
 | 
					        let pty = Pty::open().unwrap();
 | 
				
			||||||
 | 
					        let child = pty.child().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let master_fd = pty.fd.get_ref().as_raw_fd();
 | 
				
			||||||
 | 
					        let child_fd = child.fd.as_raw_fd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert!(master_fd != child_fd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,20 @@
 | 
				
			||||||
use std::{pin::Pin, process::Stdio, sync::Mutex};
 | 
					use std::{pin::Pin, process::Stdio, sync::Mutex};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use tokio::process::Command;
 | 
					use tokio::{io::AsyncWriteExt, process::Command};
 | 
				
			||||||
use tokio_stream::{
 | 
					use tokio_stream::{
 | 
				
			||||||
    wrappers::{ReceiverStream, WatchStream},
 | 
					    wrappers::{ReceiverStream, WatchStream},
 | 
				
			||||||
    Stream, StreamExt,
 | 
					    Stream, StreamExt,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use tokio_util::codec::{BytesCodec, FramedRead};
 | 
					use tokio_util::codec::{BytesCodec, FramedRead};
 | 
				
			||||||
use tonic::{Request, Response, Status};
 | 
					use tonic::{Request, Response, Status, Streaming};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{debian, health::HealthMonitor, info::Info, task::TaskBuilder};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    debian,
 | 
				
			||||||
 | 
					    health::HealthMonitor,
 | 
				
			||||||
 | 
					    info::Info,
 | 
				
			||||||
 | 
					    pty::{open_shell, Pty},
 | 
				
			||||||
 | 
					    task::TaskBuilder,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::proto::*;
 | 
					use super::proto::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,4 +174,51 @@ impl agent_server::Agent for AgentService<'static> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Response::new(Box::pin(stream)))
 | 
					        Ok(Response::new(Box::pin(stream)))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    type TerminalStream = Pin<Box<dyn Stream<Item = Result<TerminalResponse, Status>> + Send>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn terminal(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        req: Request<Streaming<TerminalRequest>>,
 | 
				
			||||||
 | 
					    ) -> AgentResult<Self::TerminalStream> {
 | 
				
			||||||
 | 
					        let mut in_stream = req.into_inner();
 | 
				
			||||||
 | 
					        let mut pty = Pty::open()?;
 | 
				
			||||||
 | 
					        let pty_clone = pty.try_clone()?;
 | 
				
			||||||
 | 
					        let pty_child = pty.child()?;
 | 
				
			||||||
 | 
					        let mut child = open_shell(pty_child)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tokio::spawn(async move {
 | 
				
			||||||
 | 
					            // TODO: Handle errors inside here
 | 
				
			||||||
 | 
					            while let Some(result) = in_stream.next().await {
 | 
				
			||||||
 | 
					                match result {
 | 
				
			||||||
 | 
					                    Ok(req) => {
 | 
				
			||||||
 | 
					                        if let Some(resize) = req.resize {
 | 
				
			||||||
 | 
					                            pty.resize_window(resize.rows as u16, resize.cols as u16)
 | 
				
			||||||
 | 
					                                .unwrap();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        pty.write_all(&req.input[..]).await.unwrap();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    Err(err) => {
 | 
				
			||||||
 | 
					                        // Log and ignore the error...
 | 
				
			||||||
 | 
					                        tracing::warn!(%err, "received an incoming stream error");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // TODO: Maybe there's a more graceful way to stop the process?
 | 
				
			||||||
 | 
					            child.kill().await.unwrap();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let out_stream = FramedRead::new(pty_clone, BytesCodec::new()).map(|inner| {
 | 
				
			||||||
 | 
					            inner
 | 
				
			||||||
 | 
					                .map(|b| TerminalResponse { output: b.to_vec() })
 | 
				
			||||||
 | 
					                .map_err(|err| {
 | 
				
			||||||
 | 
					                    tracing::error!(%err, "read error on pseudoterminal");
 | 
				
			||||||
 | 
					                    Status::internal("terminal read error")
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Response::new(Box::pin(out_stream)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
@import "tailwindcss/base";
 | 
					@import "tailwindcss/base";
 | 
				
			||||||
@import "tailwindcss/components";
 | 
					@import "tailwindcss/components";
 | 
				
			||||||
@import "tailwindcss/utilities";
 | 
					@import "tailwindcss/utilities";
 | 
				
			||||||
 | 
					@import "xterm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* This file is for your main application CSS */
 | 
					/* This file is for your main application CSS */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,42 +2,68 @@
 | 
				
			||||||
// to get started and then uncomment the line below.
 | 
					// to get started and then uncomment the line below.
 | 
				
			||||||
// import "./user_socket.js"
 | 
					// import "./user_socket.js"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// You can include dependencies in two ways.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The simplest option is to put them in assets/vendor and
 | 
					 | 
				
			||||||
// import them using relative paths:
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//     import "../vendor/some-package.js"
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Alternatively, you can `npm install some-package --prefix assets` and import
 | 
					 | 
				
			||||||
// them using a path starting with the package name:
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//     import "some-package"
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "phoenix_html"
 | 
					import "phoenix_html"
 | 
				
			||||||
import {Socket} from "phoenix"
 | 
					import { Socket } from "phoenix"
 | 
				
			||||||
import {LiveSocket} from "phoenix_live_view"
 | 
					import { LiveSocket } from "phoenix_live_view"
 | 
				
			||||||
import topbar from "../vendor/topbar"
 | 
					import topbar from "../vendor/topbar"
 | 
				
			||||||
import Alpine from "alpinejs"
 | 
					import Alpine from "alpinejs"
 | 
				
			||||||
 | 
					import { Terminal } from "xterm"
 | 
				
			||||||
 | 
					import { FitAddon } from "@xterm/addon-fit"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Alpine.start()
 | 
					let Hooks = {}
 | 
				
			||||||
window.Alpine = Alpine
 | 
					
 | 
				
			||||||
 | 
					// TODO: Move this code and xterm into a separate generated file, and load that file only when needed
 | 
				
			||||||
 | 
					Hooks.Terminal = {
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    const term = new Terminal({
 | 
				
			||||||
 | 
					      fontFamily: '"Courier New", "DejaVu Sans Mono", "Everson Mono", monospace',
 | 
				
			||||||
 | 
					      fontSize: 13,
 | 
				
			||||||
 | 
					      convertEol: true,
 | 
				
			||||||
 | 
					      theme: {
 | 
				
			||||||
 | 
					        background: "#24273a",
 | 
				
			||||||
 | 
					        foreground: "#cad3f5",
 | 
				
			||||||
 | 
					        cursor: "#f4dbd6",
 | 
				
			||||||
 | 
					        black: "#494D64",
 | 
				
			||||||
 | 
					        red: "#ed8796",
 | 
				
			||||||
 | 
					        green: "#a6da95",
 | 
				
			||||||
 | 
					        yellow: "#eed49f",
 | 
				
			||||||
 | 
					        blue: "#8aadf4",
 | 
				
			||||||
 | 
					        magenta: "#f5bde6",
 | 
				
			||||||
 | 
					        cyan: "#8bd5ca",
 | 
				
			||||||
 | 
					        white: "#b8c0e0",
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fitAddon = new FitAddon()
 | 
				
			||||||
 | 
					    const resizeObserver = new ResizeObserver(() => fitAddon.fit())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    term.loadAddon(fitAddon)
 | 
				
			||||||
 | 
					    term.open(this.el)
 | 
				
			||||||
 | 
					    term.onData(data => this.pushEventTo(this.el, "data_event", data))
 | 
				
			||||||
 | 
					    term.onResize(resize => this.pushEventTo(this.el, "resize_event", resize))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.handleEvent("data", payload => term.write(payload.data || ""))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fitAddon.fit()
 | 
				
			||||||
 | 
					    resizeObserver.observe(this.el)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
 | 
					let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
 | 
				
			||||||
let liveSocket = new LiveSocket("/live", Socket, {
 | 
					let liveSocket = new LiveSocket("/live", Socket, {
 | 
				
			||||||
  params: {_csrf_token: csrfToken},
 | 
					  params: { _csrf_token: csrfToken },
 | 
				
			||||||
  dom: {
 | 
					  dom: {
 | 
				
			||||||
    onBeforeElUpdated(from, to) {
 | 
					    onBeforeElUpdated(from, to) {
 | 
				
			||||||
      if (from._x_dataStack) {
 | 
					      if (from._x_dataStack) {
 | 
				
			||||||
        window.Alpine.clone(from, to)
 | 
					        window.Alpine.clone(from, to)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
 | 
					  hooks: Hooks,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Show progress bar on live navigation and form submits
 | 
					// Show progress bar on live navigation and form submits
 | 
				
			||||||
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
 | 
					topbar.config({ barColors: { 0: "#29d" }, shaDowColor: "rgba(0, 0, 0, .3)" })
 | 
				
			||||||
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
 | 
					window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
 | 
				
			||||||
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
 | 
					window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,3 +76,5 @@ liveSocket.connect()
 | 
				
			||||||
// >> liveSocket.disableLatencySim()
 | 
					// >> liveSocket.disableLatencySim()
 | 
				
			||||||
window.liveSocket = liveSocket
 | 
					window.liveSocket = liveSocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Alpine.start()
 | 
				
			||||||
 | 
					window.Alpine = Alpine
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								app/assets/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								app/assets/package-lock.json
									
									
									
										generated
									
									
									
								
							| 
						 | 
					@ -5,7 +5,9 @@
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "alpinejs": "^3.13.3"
 | 
					        "@xterm/addon-fit": "^0.9.0-beta.1",
 | 
				
			||||||
 | 
					        "alpinejs": "^3.13.3",
 | 
				
			||||||
 | 
					        "xterm": "^5.3.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@vue/reactivity": {
 | 
					    "node_modules/@vue/reactivity": {
 | 
				
			||||||
| 
						 | 
					@ -21,6 +23,14 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
 | 
					      "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@xterm/addon-fit": {
 | 
				
			||||||
 | 
					      "version": "0.9.0-beta.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.9.0-beta.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-HmGRUMMamUpQYuQBF2VP1LJ0xzqF85LMFfpaNu84t1Tsrl1lPKJWtqX9FDZ22Rf5q6bnKdbj44TRVAUHgDRbLA==",
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "xterm": "^5.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/alpinejs": {
 | 
					    "node_modules/alpinejs": {
 | 
				
			||||||
      "version": "3.13.3",
 | 
					      "version": "3.13.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
 | 
				
			||||||
| 
						 | 
					@ -28,6 +38,11 @@
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@vue/reactivity": "~3.1.1"
 | 
					        "@vue/reactivity": "~3.1.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/xterm": {
 | 
				
			||||||
 | 
					      "version": "5.3.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "alpinejs": "^3.13.3"
 | 
					    "@xterm/addon-fit": "^0.9.0-beta.1",
 | 
				
			||||||
 | 
					    "alpinejs": "^3.13.3",
 | 
				
			||||||
 | 
					    "xterm": "^5.3.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,6 +131,16 @@ defmodule Prymn.Agents do
 | 
				
			||||||
  def sys_update(%Agent{} = agent, request) when is_map(request),
 | 
					  def sys_update(%Agent{} = agent, request) when is_map(request),
 | 
				
			||||||
    do: sys_update(agent, struct(SysUpdateRequest, request))
 | 
					    do: sys_update(agent, struct(SysUpdateRequest, request))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def terminal(%Agent{} = agent) do
 | 
				
			||||||
 | 
					    # TODO: Find a better solve for bi-directional GRPC stream
 | 
				
			||||||
 | 
					    with {:ok, channel} <- get_channel(agent),
 | 
				
			||||||
 | 
					         stream <- Stub.terminal(channel) do
 | 
				
			||||||
 | 
					      stream
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      {:error, error} -> {:error, error}
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  defp get_channel(%Agent{} = agent) do
 | 
					  defp get_channel(%Agent{} = agent) do
 | 
				
			||||||
    case start_connection(agent.host_address) do
 | 
					    case start_connection(agent.host_address) do
 | 
				
			||||||
      {:ok, pid} -> {:ok, Connection.get_channel(pid)}
 | 
					      {:ok, pid} -> {:ok, Connection.get_channel(pid)}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ defmodule Prymn.Application do
 | 
				
			||||||
      {Finch, name: Prymn.Finch},
 | 
					      {Finch, name: Prymn.Finch},
 | 
				
			||||||
      {Oban, Application.fetch_env!(:prymn, Oban)},
 | 
					      {Oban, Application.fetch_env!(:prymn, Oban)},
 | 
				
			||||||
      Prymn.Agents.Supervisor,
 | 
					      Prymn.Agents.Supervisor,
 | 
				
			||||||
 | 
					      {Task.Supervisor, name: Prymn.TaskSupervisor},
 | 
				
			||||||
      PrymnWeb.Endpoint
 | 
					      PrymnWeb.Endpoint
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ defmodule PrymnWeb.SystemInfo do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  require Logger
 | 
					  require Logger
 | 
				
			||||||
  alias Phoenix.LiveView.AsyncResult
 | 
					  alias Phoenix.LiveView.AsyncResult
 | 
				
			||||||
 | 
					  alias PrymnProto.Prymn.SysInfoResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @impl true
 | 
					  @impl true
 | 
				
			||||||
  def update(assigns, socket) do
 | 
					  def update(assigns, socket) do
 | 
				
			||||||
| 
						 | 
					@ -59,7 +60,7 @@ defmodule PrymnWeb.SystemInfo do
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def handle_async(:get_sys_info, {:ok, {:ok, sys_info}}, socket) do
 | 
					  def handle_async(:get_sys_info, {:ok, %SysInfoResponse{} = sys_info}, socket) do
 | 
				
			||||||
    %{sys_info: sys_info_result, agent: agent} = socket.assigns
 | 
					    %{sys_info: sys_info_result, agent: agent} = socket.assigns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {:noreply,
 | 
					    {:noreply,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										120
									
								
								app/lib/prymn_web/components/terminal.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								app/lib/prymn_web/components/terminal.ex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,120 @@
 | 
				
			||||||
 | 
					defmodule PrymnWeb.Terminal do
 | 
				
			||||||
 | 
					  use PrymnWeb, :live_component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  alias PrymnProto.Prymn.TerminalRequest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @impl true
 | 
				
			||||||
 | 
					  def mount(socket) do
 | 
				
			||||||
 | 
					    {:ok, assign(socket, :open, false)}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @impl true
 | 
				
			||||||
 | 
					  def update(assigns, socket) do
 | 
				
			||||||
 | 
					    socket =
 | 
				
			||||||
 | 
					      if assigns[:data],
 | 
				
			||||||
 | 
					        do: push_event(socket, "data", %{"data" => assigns[:data]}),
 | 
				
			||||||
 | 
					        else: socket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {:ok, assign(socket, assigns)}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @impl true
 | 
				
			||||||
 | 
					  def render(assigns) do
 | 
				
			||||||
 | 
					    ~H"""
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <PrymnWeb.Button.primary
 | 
				
			||||||
 | 
					        :if={not @open}
 | 
				
			||||||
 | 
					        type="button"
 | 
				
			||||||
 | 
					        phx-target={@myself}
 | 
				
			||||||
 | 
					        phx-click="open_terminal"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        Open Terminal
 | 
				
			||||||
 | 
					      </PrymnWeb.Button.primary>
 | 
				
			||||||
 | 
					      <PrymnWeb.Button.primary
 | 
				
			||||||
 | 
					        :if={@open}
 | 
				
			||||||
 | 
					        type="button"
 | 
				
			||||||
 | 
					        phx-target={@myself}
 | 
				
			||||||
 | 
					        phx-click="close_terminal"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        Close Terminal
 | 
				
			||||||
 | 
					      </PrymnWeb.Button.primary>
 | 
				
			||||||
 | 
					      <div :if={@open} class="mt-2 bg-black p-2">
 | 
				
			||||||
 | 
					        <div phx-hook="Terminal" id="terminal" />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @impl true
 | 
				
			||||||
 | 
					  def handle_event("open_terminal", _params, socket) do
 | 
				
			||||||
 | 
					    agent = Prymn.Agents.from_server(socket.assigns.server)
 | 
				
			||||||
 | 
					    pid = self()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Task.Supervisor.start_child(Prymn.TaskSupervisor, fn ->
 | 
				
			||||||
 | 
					      # FIXME: Have to wrap this in a Task because gun sends unsolicited messages
 | 
				
			||||||
 | 
					      # to calling process
 | 
				
			||||||
 | 
					      stream = Prymn.Agents.terminal(agent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {:ok, mux_pid} =
 | 
				
			||||||
 | 
					        Task.Supervisor.start_child(Prymn.TaskSupervisor, fn -> receive_loop(stream) end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      send_update(pid, PrymnWeb.Terminal, id: "terminal", mux_pid: mux_pid, open: true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case GRPC.Stub.recv(stream, timeout: :infinity) do
 | 
				
			||||||
 | 
					        {:ok, stream} ->
 | 
				
			||||||
 | 
					          Enum.map(stream, fn
 | 
				
			||||||
 | 
					            {:ok, %{output: data}} ->
 | 
				
			||||||
 | 
					              send(mux_pid, :data)
 | 
				
			||||||
 | 
					              send_update(pid, PrymnWeb.Terminal, id: "terminal", data: data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {:error, _err} ->
 | 
				
			||||||
 | 
					              send_update(pid, PrymnWeb.Terminal, id: "terminal", open: false)
 | 
				
			||||||
 | 
					          end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {:error, error} ->
 | 
				
			||||||
 | 
					          dbg(error)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {:noreply, socket}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_event("close_terminal", _params, socket) do
 | 
				
			||||||
 | 
					    send(socket.assigns.mux_pid, :close)
 | 
				
			||||||
 | 
					    {:noreply, assign(socket, :open, false)}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_event("data_event", data, socket) when is_binary(data) do
 | 
				
			||||||
 | 
					    send(socket.assigns.mux_pid, {:data_event, data})
 | 
				
			||||||
 | 
					    {:noreply, socket}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_event("resize_event", %{"cols" => cols, "rows" => rows}, socket) do
 | 
				
			||||||
 | 
					    send(socket.assigns.mux_pid, {:resize_event, rows, cols})
 | 
				
			||||||
 | 
					    {:noreply, socket}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  defp receive_loop(stream) do
 | 
				
			||||||
 | 
					    receive do
 | 
				
			||||||
 | 
					      {:data_event, data} ->
 | 
				
			||||||
 | 
					        GRPC.Stub.send_request(stream, %TerminalRequest{input: data})
 | 
				
			||||||
 | 
					        receive_loop(stream)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {:resize_event, rows, cols} ->
 | 
				
			||||||
 | 
					        GRPC.Stub.send_request(stream, %TerminalRequest{
 | 
				
			||||||
 | 
					          resize: %TerminalRequest.Resize{rows: rows, cols: cols}
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        receive_loop(stream)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      :data ->
 | 
				
			||||||
 | 
					        receive_loop(stream)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      :close ->
 | 
				
			||||||
 | 
					        GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
 | 
				
			||||||
 | 
					    after
 | 
				
			||||||
 | 
					      120_000 ->
 | 
				
			||||||
 | 
					        GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -71,20 +71,18 @@ defmodule PrymnWeb.ServerLive.Show do
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div :if={@server.status == :registered} class="my-10">
 | 
					      <form phx-change="change_dry_run">
 | 
				
			||||||
 | 
					        <.input type="checkbox" name="dry_run" value={@dry_run} label="Enable dry-run operations" />
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					      <div :if={@server.status == :registered} class="my-10 space-y-5 divide-y">
 | 
				
			||||||
        <.live_component
 | 
					        <.live_component
 | 
				
			||||||
          id={"system_info-#{@server.name}"}
 | 
					          id={"system_info-#{@server.name}"}
 | 
				
			||||||
          module={PrymnWeb.SystemInfo}
 | 
					          module={PrymnWeb.SystemInfo}
 | 
				
			||||||
          agent={assigns[:agent]}
 | 
					          agent={assigns[:agent]}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <section class="mt-4">
 | 
					        <section>
 | 
				
			||||||
          <form phx-change="change_dry_run">
 | 
					          <h2 class="my-5 text-xl">System</h2>
 | 
				
			||||||
            <.input type="checkbox" name="dry_run" value={@dry_run} label="Enable dry-run operations" />
 | 
					          <p>
 | 
				
			||||||
          </form>
 | 
					 | 
				
			||||||
        </section>
 | 
					 | 
				
			||||||
        <section class="mt-4">
 | 
					 | 
				
			||||||
          <h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">System</h2>
 | 
					 | 
				
			||||||
          <p class="mt-4">
 | 
					 | 
				
			||||||
            Updates: <%= 0 %> pending updates.
 | 
					            Updates: <%= 0 %> pending updates.
 | 
				
			||||||
            <Button.primary type="button" class="ml-4" phx-click="system_update">
 | 
					            <Button.primary type="button" class="ml-4" phx-click="system_update">
 | 
				
			||||||
              Update now
 | 
					              Update now
 | 
				
			||||||
| 
						 | 
					@ -94,32 +92,11 @@ defmodule PrymnWeb.ServerLive.Show do
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
          </p>
 | 
					          </p>
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
        <section class="mt-4">
 | 
					        <section>
 | 
				
			||||||
          <h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">
 | 
					          <h2 class="my-5 text-xl">
 | 
				
			||||||
            Backups
 | 
					            Terminal
 | 
				
			||||||
          </h2>
 | 
					          </h2>
 | 
				
			||||||
          <.table id="backups" rows={[%{date: "2023-10-11"}, %{date: "2023-10-10"}]}>
 | 
					          <.live_component id="terminal" module={PrymnWeb.Terminal} server={@server} />
 | 
				
			||||||
            <:col :let={backup} label="Date"><%= backup.date %></:col>
 | 
					 | 
				
			||||||
            <:action>
 | 
					 | 
				
			||||||
              <Button.primary>Restore</Button.primary>
 | 
					 | 
				
			||||||
            </:action>
 | 
					 | 
				
			||||||
          </.table>
 | 
					 | 
				
			||||||
        </section>
 | 
					 | 
				
			||||||
        <section class="mt-4">
 | 
					 | 
				
			||||||
          <h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">
 | 
					 | 
				
			||||||
            Manage Services
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <.table
 | 
					 | 
				
			||||||
            id="services"
 | 
					 | 
				
			||||||
            rows={[%{name: "mariadb", status: "Active"}, %{name: "php8.0", status: "Disabled"}]}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <:col :let={service} label="Service"><%= service.name %></:col>
 | 
					 | 
				
			||||||
            <:col :let={service} label="Status"><%= service.status %></:col>
 | 
					 | 
				
			||||||
            <:action>
 | 
					 | 
				
			||||||
              <Button.primary>Activate</Button.primary>
 | 
					 | 
				
			||||||
              <Button.secondary>Deactivate</Button.secondary>
 | 
					 | 
				
			||||||
            </:action>
 | 
					 | 
				
			||||||
          </.table>
 | 
					 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <.back navigate={~p"/servers"}>Back to servers</.back>
 | 
					      <.back navigate={~p"/servers"}>Back to servers</.back>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,9 +69,24 @@ message SysUpdateResponse {
 | 
				
			||||||
    int32 progress = 2;
 | 
					    int32 progress = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message TerminalRequest {
 | 
				
			||||||
 | 
					    message Resize {
 | 
				
			||||||
 | 
					        uint32 rows = 1;
 | 
				
			||||||
 | 
					        uint32 cols = 2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bytes input = 1;
 | 
				
			||||||
 | 
					    optional Resize resize = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message TerminalResponse {
 | 
				
			||||||
 | 
					    bytes output = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
service Agent {
 | 
					service Agent {
 | 
				
			||||||
    rpc Health(google.protobuf.Empty) returns (stream HealthResponse);
 | 
					    rpc Health(google.protobuf.Empty) returns (stream HealthResponse);
 | 
				
			||||||
    rpc Exec(ExecRequest) returns (stream ExecResponse);
 | 
					    rpc Exec(ExecRequest) returns (stream ExecResponse);
 | 
				
			||||||
 | 
					    rpc Terminal(stream TerminalRequest) returns (stream TerminalResponse);
 | 
				
			||||||
    rpc GetSysInfo(google.protobuf.Empty) returns (SysInfoResponse);
 | 
					    rpc GetSysInfo(google.protobuf.Empty) returns (SysInfoResponse);
 | 
				
			||||||
    rpc SysUpdate(SysUpdateRequest) returns (stream SysUpdateResponse);
 | 
					    rpc SysUpdate(SysUpdateRequest) returns (stream SysUpdateResponse);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue