├── .gitignore ├── LICENSE ├── README.md ├── client └── src │ ├── index.html │ ├── lib │ ├── xterm.css │ ├── xterm.js │ └── xterm.js.map │ ├── script.js │ ├── style.css │ └── wspty.js ├── docs └── ss_mc.png ├── run ├── run.bat ├── server-python3 ├── config.py ├── logs.py ├── requirements.txt ├── server.py ├── test │ └── test.py ├── websocket_terminal.py └── wspty │ ├── BaseTerminal.py │ ├── EchoTerminal.py │ ├── EncodedTerminal.py │ ├── PromptTerminal.py │ ├── SocketTerminal.py │ ├── SshTerminal.py │ ├── WebsocketBinding.py │ └── pipe.py ├── setup └── setup.bat /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | __pycache__/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tamás Hegedűs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # websocket_terminal 2 | 3 | A cross-platform python3 server that bridges either ssh or raw tcp to the browser via websocket. 4 | 5 | # Quickstart 6 | 7 | 8 | ### 1. Clone and start 9 | 10 | ``` 11 | git clone https://github.com/sorgloomer/websocket_terminal.git 12 | cd websocket_terminal 13 | pip3 install -r requirements.txt 14 | python3 websocket_terminal.py 15 | ``` 16 | 17 | ### 2. Navigate browser 18 | 19 | Open one of the following links in a browser: 20 | 21 | Local ssh: 22 | 23 | ``` 24 | http://localhost:5002?kind=ssh&username=&password= 25 | ``` 26 | 27 | Remote ssh: 28 | 29 | ``` 30 | http://localhost:5002?kind=ssh&username=&password=&hostname= 31 | ``` 32 | 33 | ### 3. Enjoy 34 | 35 | ![Screenshot of mc in the browser](docs/ss_mc.png) 36 | 37 | # Remarks 38 | 39 | Currently only password authentication is supported for ssh. The username and password is sent in plaintext via **http or https** in the default implementation! 40 | 41 | # Credits 42 | 43 | Thanks to aluzzardi for the wssh project, which inspired this one. The websocket and json based protocol is entirely the same as in wssh. 44 | 45 | Greenlet and wsgi implementation: eventlet 46 | 47 | Python ssh client: paramiko 48 | 49 | Browser side terminal: xterm.js 50 | -------------------------------------------------------------------------------- /client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | wspty 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/src/lib/xterm.css: -------------------------------------------------------------------------------- 1 | /** 2 | * xterm.js: xterm, in the browser 3 | * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) 4 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 5 | * https://github.com/chjj/term.js 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /* 35 | * Default style for xterm.js 36 | */ 37 | 38 | .terminal { 39 | background-color: #000; 40 | color: #fff; 41 | font-family: courier-new, courier, monospace; 42 | font-feature-settings: "liga" 0; 43 | position: relative; 44 | } 45 | 46 | .terminal.focus, 47 | .terminal:focus { 48 | outline: none; 49 | } 50 | 51 | .terminal .xterm-helpers { 52 | position: absolute; 53 | top: 0; 54 | } 55 | 56 | .terminal .xterm-helper-textarea { 57 | /* 58 | * HACK: to fix IE's blinking cursor 59 | * Move textarea out of the screen to the far left, so that the cursor is not visible. 60 | */ 61 | position: absolute; 62 | opacity: 0; 63 | left: -9999em; 64 | top: 0; 65 | width: 0; 66 | height: 0; 67 | z-index: -10; 68 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 69 | white-space: nowrap; 70 | overflow: hidden; 71 | resize: none; 72 | } 73 | 74 | .terminal a { 75 | color: inherit; 76 | text-decoration: none; 77 | } 78 | 79 | .terminal a:hover { 80 | cursor: pointer; 81 | text-decoration: underline; 82 | } 83 | 84 | .terminal a.xterm-invalid-link:hover { 85 | cursor: text; 86 | text-decoration: none; 87 | } 88 | 89 | .terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor { 90 | background-color: #fff; 91 | color: #000; 92 | } 93 | 94 | .terminal:not(.focus) .terminal-cursor { 95 | outline: 1px solid #fff; 96 | outline-offset: -1px; 97 | background-color: transparent; 98 | } 99 | 100 | .terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor { 101 | background-color: transparent; 102 | color: inherit; 103 | } 104 | 105 | .terminal.xterm-cursor-style-bar .terminal-cursor, 106 | .terminal.xterm-cursor-style-underline .terminal-cursor { 107 | position: relative; 108 | } 109 | .terminal.xterm-cursor-style-bar .terminal-cursor::before, 110 | .terminal.xterm-cursor-style-underline .terminal-cursor::before { 111 | content: ""; 112 | display: block; 113 | position: absolute; 114 | background-color: #fff; 115 | } 116 | .terminal.xterm-cursor-style-bar .terminal-cursor::before { 117 | top: 0; 118 | bottom: 0; 119 | left: 0; 120 | width: 1px; 121 | } 122 | .terminal.xterm-cursor-style-underline .terminal-cursor::before { 123 | bottom: 0; 124 | left: 0; 125 | right: 0; 126 | height: 1px; 127 | } 128 | .terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before, 129 | .terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before { 130 | background-color: transparent; 131 | } 132 | .terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before, 133 | .terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before { 134 | background-color: #fff; 135 | } 136 | 137 | .terminal .composition-view { 138 | background: #000; 139 | color: #FFF; 140 | display: none; 141 | position: absolute; 142 | white-space: nowrap; 143 | z-index: 1; 144 | } 145 | 146 | .terminal .composition-view.active { 147 | display: block; 148 | } 149 | 150 | .terminal .xterm-viewport { 151 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 152 | background-color: #000; 153 | overflow-y: scroll; 154 | } 155 | 156 | .terminal .xterm-wide-char, 157 | .terminal .xterm-normal-char { 158 | display: inline-block; 159 | } 160 | 161 | .terminal .xterm-rows { 162 | position: absolute; 163 | left: 0; 164 | top: 0; 165 | } 166 | 167 | .terminal .xterm-rows > div { 168 | /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */ 169 | white-space: nowrap; 170 | } 171 | 172 | .terminal .xterm-scroll-area { 173 | visibility: hidden; 174 | } 175 | 176 | .terminal .xterm-char-measure-element { 177 | display: inline-block; 178 | visibility: hidden; 179 | position: absolute; 180 | left: -9999em; 181 | } 182 | 183 | /* 184 | * Determine default colors for xterm.js 185 | */ 186 | .terminal .xterm-bold { 187 | font-weight: bold; 188 | } 189 | 190 | .terminal .xterm-underline { 191 | text-decoration: underline; 192 | } 193 | 194 | .terminal .xterm-blink { 195 | text-decoration: blink; 196 | } 197 | 198 | .terminal .xterm-hidden { 199 | visibility: hidden; 200 | } 201 | 202 | .terminal .xterm-color-0 { 203 | color: #2e3436; 204 | } 205 | 206 | .terminal .xterm-bg-color-0 { 207 | background-color: #2e3436; 208 | } 209 | 210 | .terminal .xterm-color-1 { 211 | color: #cc0000; 212 | } 213 | 214 | .terminal .xterm-bg-color-1 { 215 | background-color: #cc0000; 216 | } 217 | 218 | .terminal .xterm-color-2 { 219 | color: #4e9a06; 220 | } 221 | 222 | .terminal .xterm-bg-color-2 { 223 | background-color: #4e9a06; 224 | } 225 | 226 | .terminal .xterm-color-3 { 227 | color: #c4a000; 228 | } 229 | 230 | .terminal .xterm-bg-color-3 { 231 | background-color: #c4a000; 232 | } 233 | 234 | .terminal .xterm-color-4 { 235 | color: #3465a4; 236 | } 237 | 238 | .terminal .xterm-bg-color-4 { 239 | background-color: #3465a4; 240 | } 241 | 242 | .terminal .xterm-color-5 { 243 | color: #75507b; 244 | } 245 | 246 | .terminal .xterm-bg-color-5 { 247 | background-color: #75507b; 248 | } 249 | 250 | .terminal .xterm-color-6 { 251 | color: #06989a; 252 | } 253 | 254 | .terminal .xterm-bg-color-6 { 255 | background-color: #06989a; 256 | } 257 | 258 | .terminal .xterm-color-7 { 259 | color: #d3d7cf; 260 | } 261 | 262 | .terminal .xterm-bg-color-7 { 263 | background-color: #d3d7cf; 264 | } 265 | 266 | .terminal .xterm-color-8 { 267 | color: #555753; 268 | } 269 | 270 | .terminal .xterm-bg-color-8 { 271 | background-color: #555753; 272 | } 273 | 274 | .terminal .xterm-color-9 { 275 | color: #ef2929; 276 | } 277 | 278 | .terminal .xterm-bg-color-9 { 279 | background-color: #ef2929; 280 | } 281 | 282 | .terminal .xterm-color-10 { 283 | color: #8ae234; 284 | } 285 | 286 | .terminal .xterm-bg-color-10 { 287 | background-color: #8ae234; 288 | } 289 | 290 | .terminal .xterm-color-11 { 291 | color: #fce94f; 292 | } 293 | 294 | .terminal .xterm-bg-color-11 { 295 | background-color: #fce94f; 296 | } 297 | 298 | .terminal .xterm-color-12 { 299 | color: #729fcf; 300 | } 301 | 302 | .terminal .xterm-bg-color-12 { 303 | background-color: #729fcf; 304 | } 305 | 306 | .terminal .xterm-color-13 { 307 | color: #ad7fa8; 308 | } 309 | 310 | .terminal .xterm-bg-color-13 { 311 | background-color: #ad7fa8; 312 | } 313 | 314 | .terminal .xterm-color-14 { 315 | color: #34e2e2; 316 | } 317 | 318 | .terminal .xterm-bg-color-14 { 319 | background-color: #34e2e2; 320 | } 321 | 322 | .terminal .xterm-color-15 { 323 | color: #eeeeec; 324 | } 325 | 326 | .terminal .xterm-bg-color-15 { 327 | background-color: #eeeeec; 328 | } 329 | 330 | .terminal .xterm-color-16 { 331 | color: #000000; 332 | } 333 | 334 | .terminal .xterm-bg-color-16 { 335 | background-color: #000000; 336 | } 337 | 338 | .terminal .xterm-color-17 { 339 | color: #00005f; 340 | } 341 | 342 | .terminal .xterm-bg-color-17 { 343 | background-color: #00005f; 344 | } 345 | 346 | .terminal .xterm-color-18 { 347 | color: #000087; 348 | } 349 | 350 | .terminal .xterm-bg-color-18 { 351 | background-color: #000087; 352 | } 353 | 354 | .terminal .xterm-color-19 { 355 | color: #0000af; 356 | } 357 | 358 | .terminal .xterm-bg-color-19 { 359 | background-color: #0000af; 360 | } 361 | 362 | .terminal .xterm-color-20 { 363 | color: #0000d7; 364 | } 365 | 366 | .terminal .xterm-bg-color-20 { 367 | background-color: #0000d7; 368 | } 369 | 370 | .terminal .xterm-color-21 { 371 | color: #0000ff; 372 | } 373 | 374 | .terminal .xterm-bg-color-21 { 375 | background-color: #0000ff; 376 | } 377 | 378 | .terminal .xterm-color-22 { 379 | color: #005f00; 380 | } 381 | 382 | .terminal .xterm-bg-color-22 { 383 | background-color: #005f00; 384 | } 385 | 386 | .terminal .xterm-color-23 { 387 | color: #005f5f; 388 | } 389 | 390 | .terminal .xterm-bg-color-23 { 391 | background-color: #005f5f; 392 | } 393 | 394 | .terminal .xterm-color-24 { 395 | color: #005f87; 396 | } 397 | 398 | .terminal .xterm-bg-color-24 { 399 | background-color: #005f87; 400 | } 401 | 402 | .terminal .xterm-color-25 { 403 | color: #005faf; 404 | } 405 | 406 | .terminal .xterm-bg-color-25 { 407 | background-color: #005faf; 408 | } 409 | 410 | .terminal .xterm-color-26 { 411 | color: #005fd7; 412 | } 413 | 414 | .terminal .xterm-bg-color-26 { 415 | background-color: #005fd7; 416 | } 417 | 418 | .terminal .xterm-color-27 { 419 | color: #005fff; 420 | } 421 | 422 | .terminal .xterm-bg-color-27 { 423 | background-color: #005fff; 424 | } 425 | 426 | .terminal .xterm-color-28 { 427 | color: #008700; 428 | } 429 | 430 | .terminal .xterm-bg-color-28 { 431 | background-color: #008700; 432 | } 433 | 434 | .terminal .xterm-color-29 { 435 | color: #00875f; 436 | } 437 | 438 | .terminal .xterm-bg-color-29 { 439 | background-color: #00875f; 440 | } 441 | 442 | .terminal .xterm-color-30 { 443 | color: #008787; 444 | } 445 | 446 | .terminal .xterm-bg-color-30 { 447 | background-color: #008787; 448 | } 449 | 450 | .terminal .xterm-color-31 { 451 | color: #0087af; 452 | } 453 | 454 | .terminal .xterm-bg-color-31 { 455 | background-color: #0087af; 456 | } 457 | 458 | .terminal .xterm-color-32 { 459 | color: #0087d7; 460 | } 461 | 462 | .terminal .xterm-bg-color-32 { 463 | background-color: #0087d7; 464 | } 465 | 466 | .terminal .xterm-color-33 { 467 | color: #0087ff; 468 | } 469 | 470 | .terminal .xterm-bg-color-33 { 471 | background-color: #0087ff; 472 | } 473 | 474 | .terminal .xterm-color-34 { 475 | color: #00af00; 476 | } 477 | 478 | .terminal .xterm-bg-color-34 { 479 | background-color: #00af00; 480 | } 481 | 482 | .terminal .xterm-color-35 { 483 | color: #00af5f; 484 | } 485 | 486 | .terminal .xterm-bg-color-35 { 487 | background-color: #00af5f; 488 | } 489 | 490 | .terminal .xterm-color-36 { 491 | color: #00af87; 492 | } 493 | 494 | .terminal .xterm-bg-color-36 { 495 | background-color: #00af87; 496 | } 497 | 498 | .terminal .xterm-color-37 { 499 | color: #00afaf; 500 | } 501 | 502 | .terminal .xterm-bg-color-37 { 503 | background-color: #00afaf; 504 | } 505 | 506 | .terminal .xterm-color-38 { 507 | color: #00afd7; 508 | } 509 | 510 | .terminal .xterm-bg-color-38 { 511 | background-color: #00afd7; 512 | } 513 | 514 | .terminal .xterm-color-39 { 515 | color: #00afff; 516 | } 517 | 518 | .terminal .xterm-bg-color-39 { 519 | background-color: #00afff; 520 | } 521 | 522 | .terminal .xterm-color-40 { 523 | color: #00d700; 524 | } 525 | 526 | .terminal .xterm-bg-color-40 { 527 | background-color: #00d700; 528 | } 529 | 530 | .terminal .xterm-color-41 { 531 | color: #00d75f; 532 | } 533 | 534 | .terminal .xterm-bg-color-41 { 535 | background-color: #00d75f; 536 | } 537 | 538 | .terminal .xterm-color-42 { 539 | color: #00d787; 540 | } 541 | 542 | .terminal .xterm-bg-color-42 { 543 | background-color: #00d787; 544 | } 545 | 546 | .terminal .xterm-color-43 { 547 | color: #00d7af; 548 | } 549 | 550 | .terminal .xterm-bg-color-43 { 551 | background-color: #00d7af; 552 | } 553 | 554 | .terminal .xterm-color-44 { 555 | color: #00d7d7; 556 | } 557 | 558 | .terminal .xterm-bg-color-44 { 559 | background-color: #00d7d7; 560 | } 561 | 562 | .terminal .xterm-color-45 { 563 | color: #00d7ff; 564 | } 565 | 566 | .terminal .xterm-bg-color-45 { 567 | background-color: #00d7ff; 568 | } 569 | 570 | .terminal .xterm-color-46 { 571 | color: #00ff00; 572 | } 573 | 574 | .terminal .xterm-bg-color-46 { 575 | background-color: #00ff00; 576 | } 577 | 578 | .terminal .xterm-color-47 { 579 | color: #00ff5f; 580 | } 581 | 582 | .terminal .xterm-bg-color-47 { 583 | background-color: #00ff5f; 584 | } 585 | 586 | .terminal .xterm-color-48 { 587 | color: #00ff87; 588 | } 589 | 590 | .terminal .xterm-bg-color-48 { 591 | background-color: #00ff87; 592 | } 593 | 594 | .terminal .xterm-color-49 { 595 | color: #00ffaf; 596 | } 597 | 598 | .terminal .xterm-bg-color-49 { 599 | background-color: #00ffaf; 600 | } 601 | 602 | .terminal .xterm-color-50 { 603 | color: #00ffd7; 604 | } 605 | 606 | .terminal .xterm-bg-color-50 { 607 | background-color: #00ffd7; 608 | } 609 | 610 | .terminal .xterm-color-51 { 611 | color: #00ffff; 612 | } 613 | 614 | .terminal .xterm-bg-color-51 { 615 | background-color: #00ffff; 616 | } 617 | 618 | .terminal .xterm-color-52 { 619 | color: #5f0000; 620 | } 621 | 622 | .terminal .xterm-bg-color-52 { 623 | background-color: #5f0000; 624 | } 625 | 626 | .terminal .xterm-color-53 { 627 | color: #5f005f; 628 | } 629 | 630 | .terminal .xterm-bg-color-53 { 631 | background-color: #5f005f; 632 | } 633 | 634 | .terminal .xterm-color-54 { 635 | color: #5f0087; 636 | } 637 | 638 | .terminal .xterm-bg-color-54 { 639 | background-color: #5f0087; 640 | } 641 | 642 | .terminal .xterm-color-55 { 643 | color: #5f00af; 644 | } 645 | 646 | .terminal .xterm-bg-color-55 { 647 | background-color: #5f00af; 648 | } 649 | 650 | .terminal .xterm-color-56 { 651 | color: #5f00d7; 652 | } 653 | 654 | .terminal .xterm-bg-color-56 { 655 | background-color: #5f00d7; 656 | } 657 | 658 | .terminal .xterm-color-57 { 659 | color: #5f00ff; 660 | } 661 | 662 | .terminal .xterm-bg-color-57 { 663 | background-color: #5f00ff; 664 | } 665 | 666 | .terminal .xterm-color-58 { 667 | color: #5f5f00; 668 | } 669 | 670 | .terminal .xterm-bg-color-58 { 671 | background-color: #5f5f00; 672 | } 673 | 674 | .terminal .xterm-color-59 { 675 | color: #5f5f5f; 676 | } 677 | 678 | .terminal .xterm-bg-color-59 { 679 | background-color: #5f5f5f; 680 | } 681 | 682 | .terminal .xterm-color-60 { 683 | color: #5f5f87; 684 | } 685 | 686 | .terminal .xterm-bg-color-60 { 687 | background-color: #5f5f87; 688 | } 689 | 690 | .terminal .xterm-color-61 { 691 | color: #5f5faf; 692 | } 693 | 694 | .terminal .xterm-bg-color-61 { 695 | background-color: #5f5faf; 696 | } 697 | 698 | .terminal .xterm-color-62 { 699 | color: #5f5fd7; 700 | } 701 | 702 | .terminal .xterm-bg-color-62 { 703 | background-color: #5f5fd7; 704 | } 705 | 706 | .terminal .xterm-color-63 { 707 | color: #5f5fff; 708 | } 709 | 710 | .terminal .xterm-bg-color-63 { 711 | background-color: #5f5fff; 712 | } 713 | 714 | .terminal .xterm-color-64 { 715 | color: #5f8700; 716 | } 717 | 718 | .terminal .xterm-bg-color-64 { 719 | background-color: #5f8700; 720 | } 721 | 722 | .terminal .xterm-color-65 { 723 | color: #5f875f; 724 | } 725 | 726 | .terminal .xterm-bg-color-65 { 727 | background-color: #5f875f; 728 | } 729 | 730 | .terminal .xterm-color-66 { 731 | color: #5f8787; 732 | } 733 | 734 | .terminal .xterm-bg-color-66 { 735 | background-color: #5f8787; 736 | } 737 | 738 | .terminal .xterm-color-67 { 739 | color: #5f87af; 740 | } 741 | 742 | .terminal .xterm-bg-color-67 { 743 | background-color: #5f87af; 744 | } 745 | 746 | .terminal .xterm-color-68 { 747 | color: #5f87d7; 748 | } 749 | 750 | .terminal .xterm-bg-color-68 { 751 | background-color: #5f87d7; 752 | } 753 | 754 | .terminal .xterm-color-69 { 755 | color: #5f87ff; 756 | } 757 | 758 | .terminal .xterm-bg-color-69 { 759 | background-color: #5f87ff; 760 | } 761 | 762 | .terminal .xterm-color-70 { 763 | color: #5faf00; 764 | } 765 | 766 | .terminal .xterm-bg-color-70 { 767 | background-color: #5faf00; 768 | } 769 | 770 | .terminal .xterm-color-71 { 771 | color: #5faf5f; 772 | } 773 | 774 | .terminal .xterm-bg-color-71 { 775 | background-color: #5faf5f; 776 | } 777 | 778 | .terminal .xterm-color-72 { 779 | color: #5faf87; 780 | } 781 | 782 | .terminal .xterm-bg-color-72 { 783 | background-color: #5faf87; 784 | } 785 | 786 | .terminal .xterm-color-73 { 787 | color: #5fafaf; 788 | } 789 | 790 | .terminal .xterm-bg-color-73 { 791 | background-color: #5fafaf; 792 | } 793 | 794 | .terminal .xterm-color-74 { 795 | color: #5fafd7; 796 | } 797 | 798 | .terminal .xterm-bg-color-74 { 799 | background-color: #5fafd7; 800 | } 801 | 802 | .terminal .xterm-color-75 { 803 | color: #5fafff; 804 | } 805 | 806 | .terminal .xterm-bg-color-75 { 807 | background-color: #5fafff; 808 | } 809 | 810 | .terminal .xterm-color-76 { 811 | color: #5fd700; 812 | } 813 | 814 | .terminal .xterm-bg-color-76 { 815 | background-color: #5fd700; 816 | } 817 | 818 | .terminal .xterm-color-77 { 819 | color: #5fd75f; 820 | } 821 | 822 | .terminal .xterm-bg-color-77 { 823 | background-color: #5fd75f; 824 | } 825 | 826 | .terminal .xterm-color-78 { 827 | color: #5fd787; 828 | } 829 | 830 | .terminal .xterm-bg-color-78 { 831 | background-color: #5fd787; 832 | } 833 | 834 | .terminal .xterm-color-79 { 835 | color: #5fd7af; 836 | } 837 | 838 | .terminal .xterm-bg-color-79 { 839 | background-color: #5fd7af; 840 | } 841 | 842 | .terminal .xterm-color-80 { 843 | color: #5fd7d7; 844 | } 845 | 846 | .terminal .xterm-bg-color-80 { 847 | background-color: #5fd7d7; 848 | } 849 | 850 | .terminal .xterm-color-81 { 851 | color: #5fd7ff; 852 | } 853 | 854 | .terminal .xterm-bg-color-81 { 855 | background-color: #5fd7ff; 856 | } 857 | 858 | .terminal .xterm-color-82 { 859 | color: #5fff00; 860 | } 861 | 862 | .terminal .xterm-bg-color-82 { 863 | background-color: #5fff00; 864 | } 865 | 866 | .terminal .xterm-color-83 { 867 | color: #5fff5f; 868 | } 869 | 870 | .terminal .xterm-bg-color-83 { 871 | background-color: #5fff5f; 872 | } 873 | 874 | .terminal .xterm-color-84 { 875 | color: #5fff87; 876 | } 877 | 878 | .terminal .xterm-bg-color-84 { 879 | background-color: #5fff87; 880 | } 881 | 882 | .terminal .xterm-color-85 { 883 | color: #5fffaf; 884 | } 885 | 886 | .terminal .xterm-bg-color-85 { 887 | background-color: #5fffaf; 888 | } 889 | 890 | .terminal .xterm-color-86 { 891 | color: #5fffd7; 892 | } 893 | 894 | .terminal .xterm-bg-color-86 { 895 | background-color: #5fffd7; 896 | } 897 | 898 | .terminal .xterm-color-87 { 899 | color: #5fffff; 900 | } 901 | 902 | .terminal .xterm-bg-color-87 { 903 | background-color: #5fffff; 904 | } 905 | 906 | .terminal .xterm-color-88 { 907 | color: #870000; 908 | } 909 | 910 | .terminal .xterm-bg-color-88 { 911 | background-color: #870000; 912 | } 913 | 914 | .terminal .xterm-color-89 { 915 | color: #87005f; 916 | } 917 | 918 | .terminal .xterm-bg-color-89 { 919 | background-color: #87005f; 920 | } 921 | 922 | .terminal .xterm-color-90 { 923 | color: #870087; 924 | } 925 | 926 | .terminal .xterm-bg-color-90 { 927 | background-color: #870087; 928 | } 929 | 930 | .terminal .xterm-color-91 { 931 | color: #8700af; 932 | } 933 | 934 | .terminal .xterm-bg-color-91 { 935 | background-color: #8700af; 936 | } 937 | 938 | .terminal .xterm-color-92 { 939 | color: #8700d7; 940 | } 941 | 942 | .terminal .xterm-bg-color-92 { 943 | background-color: #8700d7; 944 | } 945 | 946 | .terminal .xterm-color-93 { 947 | color: #8700ff; 948 | } 949 | 950 | .terminal .xterm-bg-color-93 { 951 | background-color: #8700ff; 952 | } 953 | 954 | .terminal .xterm-color-94 { 955 | color: #875f00; 956 | } 957 | 958 | .terminal .xterm-bg-color-94 { 959 | background-color: #875f00; 960 | } 961 | 962 | .terminal .xterm-color-95 { 963 | color: #875f5f; 964 | } 965 | 966 | .terminal .xterm-bg-color-95 { 967 | background-color: #875f5f; 968 | } 969 | 970 | .terminal .xterm-color-96 { 971 | color: #875f87; 972 | } 973 | 974 | .terminal .xterm-bg-color-96 { 975 | background-color: #875f87; 976 | } 977 | 978 | .terminal .xterm-color-97 { 979 | color: #875faf; 980 | } 981 | 982 | .terminal .xterm-bg-color-97 { 983 | background-color: #875faf; 984 | } 985 | 986 | .terminal .xterm-color-98 { 987 | color: #875fd7; 988 | } 989 | 990 | .terminal .xterm-bg-color-98 { 991 | background-color: #875fd7; 992 | } 993 | 994 | .terminal .xterm-color-99 { 995 | color: #875fff; 996 | } 997 | 998 | .terminal .xterm-bg-color-99 { 999 | background-color: #875fff; 1000 | } 1001 | 1002 | .terminal .xterm-color-100 { 1003 | color: #878700; 1004 | } 1005 | 1006 | .terminal .xterm-bg-color-100 { 1007 | background-color: #878700; 1008 | } 1009 | 1010 | .terminal .xterm-color-101 { 1011 | color: #87875f; 1012 | } 1013 | 1014 | .terminal .xterm-bg-color-101 { 1015 | background-color: #87875f; 1016 | } 1017 | 1018 | .terminal .xterm-color-102 { 1019 | color: #878787; 1020 | } 1021 | 1022 | .terminal .xterm-bg-color-102 { 1023 | background-color: #878787; 1024 | } 1025 | 1026 | .terminal .xterm-color-103 { 1027 | color: #8787af; 1028 | } 1029 | 1030 | .terminal .xterm-bg-color-103 { 1031 | background-color: #8787af; 1032 | } 1033 | 1034 | .terminal .xterm-color-104 { 1035 | color: #8787d7; 1036 | } 1037 | 1038 | .terminal .xterm-bg-color-104 { 1039 | background-color: #8787d7; 1040 | } 1041 | 1042 | .terminal .xterm-color-105 { 1043 | color: #8787ff; 1044 | } 1045 | 1046 | .terminal .xterm-bg-color-105 { 1047 | background-color: #8787ff; 1048 | } 1049 | 1050 | .terminal .xterm-color-106 { 1051 | color: #87af00; 1052 | } 1053 | 1054 | .terminal .xterm-bg-color-106 { 1055 | background-color: #87af00; 1056 | } 1057 | 1058 | .terminal .xterm-color-107 { 1059 | color: #87af5f; 1060 | } 1061 | 1062 | .terminal .xterm-bg-color-107 { 1063 | background-color: #87af5f; 1064 | } 1065 | 1066 | .terminal .xterm-color-108 { 1067 | color: #87af87; 1068 | } 1069 | 1070 | .terminal .xterm-bg-color-108 { 1071 | background-color: #87af87; 1072 | } 1073 | 1074 | .terminal .xterm-color-109 { 1075 | color: #87afaf; 1076 | } 1077 | 1078 | .terminal .xterm-bg-color-109 { 1079 | background-color: #87afaf; 1080 | } 1081 | 1082 | .terminal .xterm-color-110 { 1083 | color: #87afd7; 1084 | } 1085 | 1086 | .terminal .xterm-bg-color-110 { 1087 | background-color: #87afd7; 1088 | } 1089 | 1090 | .terminal .xterm-color-111 { 1091 | color: #87afff; 1092 | } 1093 | 1094 | .terminal .xterm-bg-color-111 { 1095 | background-color: #87afff; 1096 | } 1097 | 1098 | .terminal .xterm-color-112 { 1099 | color: #87d700; 1100 | } 1101 | 1102 | .terminal .xterm-bg-color-112 { 1103 | background-color: #87d700; 1104 | } 1105 | 1106 | .terminal .xterm-color-113 { 1107 | color: #87d75f; 1108 | } 1109 | 1110 | .terminal .xterm-bg-color-113 { 1111 | background-color: #87d75f; 1112 | } 1113 | 1114 | .terminal .xterm-color-114 { 1115 | color: #87d787; 1116 | } 1117 | 1118 | .terminal .xterm-bg-color-114 { 1119 | background-color: #87d787; 1120 | } 1121 | 1122 | .terminal .xterm-color-115 { 1123 | color: #87d7af; 1124 | } 1125 | 1126 | .terminal .xterm-bg-color-115 { 1127 | background-color: #87d7af; 1128 | } 1129 | 1130 | .terminal .xterm-color-116 { 1131 | color: #87d7d7; 1132 | } 1133 | 1134 | .terminal .xterm-bg-color-116 { 1135 | background-color: #87d7d7; 1136 | } 1137 | 1138 | .terminal .xterm-color-117 { 1139 | color: #87d7ff; 1140 | } 1141 | 1142 | .terminal .xterm-bg-color-117 { 1143 | background-color: #87d7ff; 1144 | } 1145 | 1146 | .terminal .xterm-color-118 { 1147 | color: #87ff00; 1148 | } 1149 | 1150 | .terminal .xterm-bg-color-118 { 1151 | background-color: #87ff00; 1152 | } 1153 | 1154 | .terminal .xterm-color-119 { 1155 | color: #87ff5f; 1156 | } 1157 | 1158 | .terminal .xterm-bg-color-119 { 1159 | background-color: #87ff5f; 1160 | } 1161 | 1162 | .terminal .xterm-color-120 { 1163 | color: #87ff87; 1164 | } 1165 | 1166 | .terminal .xterm-bg-color-120 { 1167 | background-color: #87ff87; 1168 | } 1169 | 1170 | .terminal .xterm-color-121 { 1171 | color: #87ffaf; 1172 | } 1173 | 1174 | .terminal .xterm-bg-color-121 { 1175 | background-color: #87ffaf; 1176 | } 1177 | 1178 | .terminal .xterm-color-122 { 1179 | color: #87ffd7; 1180 | } 1181 | 1182 | .terminal .xterm-bg-color-122 { 1183 | background-color: #87ffd7; 1184 | } 1185 | 1186 | .terminal .xterm-color-123 { 1187 | color: #87ffff; 1188 | } 1189 | 1190 | .terminal .xterm-bg-color-123 { 1191 | background-color: #87ffff; 1192 | } 1193 | 1194 | .terminal .xterm-color-124 { 1195 | color: #af0000; 1196 | } 1197 | 1198 | .terminal .xterm-bg-color-124 { 1199 | background-color: #af0000; 1200 | } 1201 | 1202 | .terminal .xterm-color-125 { 1203 | color: #af005f; 1204 | } 1205 | 1206 | .terminal .xterm-bg-color-125 { 1207 | background-color: #af005f; 1208 | } 1209 | 1210 | .terminal .xterm-color-126 { 1211 | color: #af0087; 1212 | } 1213 | 1214 | .terminal .xterm-bg-color-126 { 1215 | background-color: #af0087; 1216 | } 1217 | 1218 | .terminal .xterm-color-127 { 1219 | color: #af00af; 1220 | } 1221 | 1222 | .terminal .xterm-bg-color-127 { 1223 | background-color: #af00af; 1224 | } 1225 | 1226 | .terminal .xterm-color-128 { 1227 | color: #af00d7; 1228 | } 1229 | 1230 | .terminal .xterm-bg-color-128 { 1231 | background-color: #af00d7; 1232 | } 1233 | 1234 | .terminal .xterm-color-129 { 1235 | color: #af00ff; 1236 | } 1237 | 1238 | .terminal .xterm-bg-color-129 { 1239 | background-color: #af00ff; 1240 | } 1241 | 1242 | .terminal .xterm-color-130 { 1243 | color: #af5f00; 1244 | } 1245 | 1246 | .terminal .xterm-bg-color-130 { 1247 | background-color: #af5f00; 1248 | } 1249 | 1250 | .terminal .xterm-color-131 { 1251 | color: #af5f5f; 1252 | } 1253 | 1254 | .terminal .xterm-bg-color-131 { 1255 | background-color: #af5f5f; 1256 | } 1257 | 1258 | .terminal .xterm-color-132 { 1259 | color: #af5f87; 1260 | } 1261 | 1262 | .terminal .xterm-bg-color-132 { 1263 | background-color: #af5f87; 1264 | } 1265 | 1266 | .terminal .xterm-color-133 { 1267 | color: #af5faf; 1268 | } 1269 | 1270 | .terminal .xterm-bg-color-133 { 1271 | background-color: #af5faf; 1272 | } 1273 | 1274 | .terminal .xterm-color-134 { 1275 | color: #af5fd7; 1276 | } 1277 | 1278 | .terminal .xterm-bg-color-134 { 1279 | background-color: #af5fd7; 1280 | } 1281 | 1282 | .terminal .xterm-color-135 { 1283 | color: #af5fff; 1284 | } 1285 | 1286 | .terminal .xterm-bg-color-135 { 1287 | background-color: #af5fff; 1288 | } 1289 | 1290 | .terminal .xterm-color-136 { 1291 | color: #af8700; 1292 | } 1293 | 1294 | .terminal .xterm-bg-color-136 { 1295 | background-color: #af8700; 1296 | } 1297 | 1298 | .terminal .xterm-color-137 { 1299 | color: #af875f; 1300 | } 1301 | 1302 | .terminal .xterm-bg-color-137 { 1303 | background-color: #af875f; 1304 | } 1305 | 1306 | .terminal .xterm-color-138 { 1307 | color: #af8787; 1308 | } 1309 | 1310 | .terminal .xterm-bg-color-138 { 1311 | background-color: #af8787; 1312 | } 1313 | 1314 | .terminal .xterm-color-139 { 1315 | color: #af87af; 1316 | } 1317 | 1318 | .terminal .xterm-bg-color-139 { 1319 | background-color: #af87af; 1320 | } 1321 | 1322 | .terminal .xterm-color-140 { 1323 | color: #af87d7; 1324 | } 1325 | 1326 | .terminal .xterm-bg-color-140 { 1327 | background-color: #af87d7; 1328 | } 1329 | 1330 | .terminal .xterm-color-141 { 1331 | color: #af87ff; 1332 | } 1333 | 1334 | .terminal .xterm-bg-color-141 { 1335 | background-color: #af87ff; 1336 | } 1337 | 1338 | .terminal .xterm-color-142 { 1339 | color: #afaf00; 1340 | } 1341 | 1342 | .terminal .xterm-bg-color-142 { 1343 | background-color: #afaf00; 1344 | } 1345 | 1346 | .terminal .xterm-color-143 { 1347 | color: #afaf5f; 1348 | } 1349 | 1350 | .terminal .xterm-bg-color-143 { 1351 | background-color: #afaf5f; 1352 | } 1353 | 1354 | .terminal .xterm-color-144 { 1355 | color: #afaf87; 1356 | } 1357 | 1358 | .terminal .xterm-bg-color-144 { 1359 | background-color: #afaf87; 1360 | } 1361 | 1362 | .terminal .xterm-color-145 { 1363 | color: #afafaf; 1364 | } 1365 | 1366 | .terminal .xterm-bg-color-145 { 1367 | background-color: #afafaf; 1368 | } 1369 | 1370 | .terminal .xterm-color-146 { 1371 | color: #afafd7; 1372 | } 1373 | 1374 | .terminal .xterm-bg-color-146 { 1375 | background-color: #afafd7; 1376 | } 1377 | 1378 | .terminal .xterm-color-147 { 1379 | color: #afafff; 1380 | } 1381 | 1382 | .terminal .xterm-bg-color-147 { 1383 | background-color: #afafff; 1384 | } 1385 | 1386 | .terminal .xterm-color-148 { 1387 | color: #afd700; 1388 | } 1389 | 1390 | .terminal .xterm-bg-color-148 { 1391 | background-color: #afd700; 1392 | } 1393 | 1394 | .terminal .xterm-color-149 { 1395 | color: #afd75f; 1396 | } 1397 | 1398 | .terminal .xterm-bg-color-149 { 1399 | background-color: #afd75f; 1400 | } 1401 | 1402 | .terminal .xterm-color-150 { 1403 | color: #afd787; 1404 | } 1405 | 1406 | .terminal .xterm-bg-color-150 { 1407 | background-color: #afd787; 1408 | } 1409 | 1410 | .terminal .xterm-color-151 { 1411 | color: #afd7af; 1412 | } 1413 | 1414 | .terminal .xterm-bg-color-151 { 1415 | background-color: #afd7af; 1416 | } 1417 | 1418 | .terminal .xterm-color-152 { 1419 | color: #afd7d7; 1420 | } 1421 | 1422 | .terminal .xterm-bg-color-152 { 1423 | background-color: #afd7d7; 1424 | } 1425 | 1426 | .terminal .xterm-color-153 { 1427 | color: #afd7ff; 1428 | } 1429 | 1430 | .terminal .xterm-bg-color-153 { 1431 | background-color: #afd7ff; 1432 | } 1433 | 1434 | .terminal .xterm-color-154 { 1435 | color: #afff00; 1436 | } 1437 | 1438 | .terminal .xterm-bg-color-154 { 1439 | background-color: #afff00; 1440 | } 1441 | 1442 | .terminal .xterm-color-155 { 1443 | color: #afff5f; 1444 | } 1445 | 1446 | .terminal .xterm-bg-color-155 { 1447 | background-color: #afff5f; 1448 | } 1449 | 1450 | .terminal .xterm-color-156 { 1451 | color: #afff87; 1452 | } 1453 | 1454 | .terminal .xterm-bg-color-156 { 1455 | background-color: #afff87; 1456 | } 1457 | 1458 | .terminal .xterm-color-157 { 1459 | color: #afffaf; 1460 | } 1461 | 1462 | .terminal .xterm-bg-color-157 { 1463 | background-color: #afffaf; 1464 | } 1465 | 1466 | .terminal .xterm-color-158 { 1467 | color: #afffd7; 1468 | } 1469 | 1470 | .terminal .xterm-bg-color-158 { 1471 | background-color: #afffd7; 1472 | } 1473 | 1474 | .terminal .xterm-color-159 { 1475 | color: #afffff; 1476 | } 1477 | 1478 | .terminal .xterm-bg-color-159 { 1479 | background-color: #afffff; 1480 | } 1481 | 1482 | .terminal .xterm-color-160 { 1483 | color: #d70000; 1484 | } 1485 | 1486 | .terminal .xterm-bg-color-160 { 1487 | background-color: #d70000; 1488 | } 1489 | 1490 | .terminal .xterm-color-161 { 1491 | color: #d7005f; 1492 | } 1493 | 1494 | .terminal .xterm-bg-color-161 { 1495 | background-color: #d7005f; 1496 | } 1497 | 1498 | .terminal .xterm-color-162 { 1499 | color: #d70087; 1500 | } 1501 | 1502 | .terminal .xterm-bg-color-162 { 1503 | background-color: #d70087; 1504 | } 1505 | 1506 | .terminal .xterm-color-163 { 1507 | color: #d700af; 1508 | } 1509 | 1510 | .terminal .xterm-bg-color-163 { 1511 | background-color: #d700af; 1512 | } 1513 | 1514 | .terminal .xterm-color-164 { 1515 | color: #d700d7; 1516 | } 1517 | 1518 | .terminal .xterm-bg-color-164 { 1519 | background-color: #d700d7; 1520 | } 1521 | 1522 | .terminal .xterm-color-165 { 1523 | color: #d700ff; 1524 | } 1525 | 1526 | .terminal .xterm-bg-color-165 { 1527 | background-color: #d700ff; 1528 | } 1529 | 1530 | .terminal .xterm-color-166 { 1531 | color: #d75f00; 1532 | } 1533 | 1534 | .terminal .xterm-bg-color-166 { 1535 | background-color: #d75f00; 1536 | } 1537 | 1538 | .terminal .xterm-color-167 { 1539 | color: #d75f5f; 1540 | } 1541 | 1542 | .terminal .xterm-bg-color-167 { 1543 | background-color: #d75f5f; 1544 | } 1545 | 1546 | .terminal .xterm-color-168 { 1547 | color: #d75f87; 1548 | } 1549 | 1550 | .terminal .xterm-bg-color-168 { 1551 | background-color: #d75f87; 1552 | } 1553 | 1554 | .terminal .xterm-color-169 { 1555 | color: #d75faf; 1556 | } 1557 | 1558 | .terminal .xterm-bg-color-169 { 1559 | background-color: #d75faf; 1560 | } 1561 | 1562 | .terminal .xterm-color-170 { 1563 | color: #d75fd7; 1564 | } 1565 | 1566 | .terminal .xterm-bg-color-170 { 1567 | background-color: #d75fd7; 1568 | } 1569 | 1570 | .terminal .xterm-color-171 { 1571 | color: #d75fff; 1572 | } 1573 | 1574 | .terminal .xterm-bg-color-171 { 1575 | background-color: #d75fff; 1576 | } 1577 | 1578 | .terminal .xterm-color-172 { 1579 | color: #d78700; 1580 | } 1581 | 1582 | .terminal .xterm-bg-color-172 { 1583 | background-color: #d78700; 1584 | } 1585 | 1586 | .terminal .xterm-color-173 { 1587 | color: #d7875f; 1588 | } 1589 | 1590 | .terminal .xterm-bg-color-173 { 1591 | background-color: #d7875f; 1592 | } 1593 | 1594 | .terminal .xterm-color-174 { 1595 | color: #d78787; 1596 | } 1597 | 1598 | .terminal .xterm-bg-color-174 { 1599 | background-color: #d78787; 1600 | } 1601 | 1602 | .terminal .xterm-color-175 { 1603 | color: #d787af; 1604 | } 1605 | 1606 | .terminal .xterm-bg-color-175 { 1607 | background-color: #d787af; 1608 | } 1609 | 1610 | .terminal .xterm-color-176 { 1611 | color: #d787d7; 1612 | } 1613 | 1614 | .terminal .xterm-bg-color-176 { 1615 | background-color: #d787d7; 1616 | } 1617 | 1618 | .terminal .xterm-color-177 { 1619 | color: #d787ff; 1620 | } 1621 | 1622 | .terminal .xterm-bg-color-177 { 1623 | background-color: #d787ff; 1624 | } 1625 | 1626 | .terminal .xterm-color-178 { 1627 | color: #d7af00; 1628 | } 1629 | 1630 | .terminal .xterm-bg-color-178 { 1631 | background-color: #d7af00; 1632 | } 1633 | 1634 | .terminal .xterm-color-179 { 1635 | color: #d7af5f; 1636 | } 1637 | 1638 | .terminal .xterm-bg-color-179 { 1639 | background-color: #d7af5f; 1640 | } 1641 | 1642 | .terminal .xterm-color-180 { 1643 | color: #d7af87; 1644 | } 1645 | 1646 | .terminal .xterm-bg-color-180 { 1647 | background-color: #d7af87; 1648 | } 1649 | 1650 | .terminal .xterm-color-181 { 1651 | color: #d7afaf; 1652 | } 1653 | 1654 | .terminal .xterm-bg-color-181 { 1655 | background-color: #d7afaf; 1656 | } 1657 | 1658 | .terminal .xterm-color-182 { 1659 | color: #d7afd7; 1660 | } 1661 | 1662 | .terminal .xterm-bg-color-182 { 1663 | background-color: #d7afd7; 1664 | } 1665 | 1666 | .terminal .xterm-color-183 { 1667 | color: #d7afff; 1668 | } 1669 | 1670 | .terminal .xterm-bg-color-183 { 1671 | background-color: #d7afff; 1672 | } 1673 | 1674 | .terminal .xterm-color-184 { 1675 | color: #d7d700; 1676 | } 1677 | 1678 | .terminal .xterm-bg-color-184 { 1679 | background-color: #d7d700; 1680 | } 1681 | 1682 | .terminal .xterm-color-185 { 1683 | color: #d7d75f; 1684 | } 1685 | 1686 | .terminal .xterm-bg-color-185 { 1687 | background-color: #d7d75f; 1688 | } 1689 | 1690 | .terminal .xterm-color-186 { 1691 | color: #d7d787; 1692 | } 1693 | 1694 | .terminal .xterm-bg-color-186 { 1695 | background-color: #d7d787; 1696 | } 1697 | 1698 | .terminal .xterm-color-187 { 1699 | color: #d7d7af; 1700 | } 1701 | 1702 | .terminal .xterm-bg-color-187 { 1703 | background-color: #d7d7af; 1704 | } 1705 | 1706 | .terminal .xterm-color-188 { 1707 | color: #d7d7d7; 1708 | } 1709 | 1710 | .terminal .xterm-bg-color-188 { 1711 | background-color: #d7d7d7; 1712 | } 1713 | 1714 | .terminal .xterm-color-189 { 1715 | color: #d7d7ff; 1716 | } 1717 | 1718 | .terminal .xterm-bg-color-189 { 1719 | background-color: #d7d7ff; 1720 | } 1721 | 1722 | .terminal .xterm-color-190 { 1723 | color: #d7ff00; 1724 | } 1725 | 1726 | .terminal .xterm-bg-color-190 { 1727 | background-color: #d7ff00; 1728 | } 1729 | 1730 | .terminal .xterm-color-191 { 1731 | color: #d7ff5f; 1732 | } 1733 | 1734 | .terminal .xterm-bg-color-191 { 1735 | background-color: #d7ff5f; 1736 | } 1737 | 1738 | .terminal .xterm-color-192 { 1739 | color: #d7ff87; 1740 | } 1741 | 1742 | .terminal .xterm-bg-color-192 { 1743 | background-color: #d7ff87; 1744 | } 1745 | 1746 | .terminal .xterm-color-193 { 1747 | color: #d7ffaf; 1748 | } 1749 | 1750 | .terminal .xterm-bg-color-193 { 1751 | background-color: #d7ffaf; 1752 | } 1753 | 1754 | .terminal .xterm-color-194 { 1755 | color: #d7ffd7; 1756 | } 1757 | 1758 | .terminal .xterm-bg-color-194 { 1759 | background-color: #d7ffd7; 1760 | } 1761 | 1762 | .terminal .xterm-color-195 { 1763 | color: #d7ffff; 1764 | } 1765 | 1766 | .terminal .xterm-bg-color-195 { 1767 | background-color: #d7ffff; 1768 | } 1769 | 1770 | .terminal .xterm-color-196 { 1771 | color: #ff0000; 1772 | } 1773 | 1774 | .terminal .xterm-bg-color-196 { 1775 | background-color: #ff0000; 1776 | } 1777 | 1778 | .terminal .xterm-color-197 { 1779 | color: #ff005f; 1780 | } 1781 | 1782 | .terminal .xterm-bg-color-197 { 1783 | background-color: #ff005f; 1784 | } 1785 | 1786 | .terminal .xterm-color-198 { 1787 | color: #ff0087; 1788 | } 1789 | 1790 | .terminal .xterm-bg-color-198 { 1791 | background-color: #ff0087; 1792 | } 1793 | 1794 | .terminal .xterm-color-199 { 1795 | color: #ff00af; 1796 | } 1797 | 1798 | .terminal .xterm-bg-color-199 { 1799 | background-color: #ff00af; 1800 | } 1801 | 1802 | .terminal .xterm-color-200 { 1803 | color: #ff00d7; 1804 | } 1805 | 1806 | .terminal .xterm-bg-color-200 { 1807 | background-color: #ff00d7; 1808 | } 1809 | 1810 | .terminal .xterm-color-201 { 1811 | color: #ff00ff; 1812 | } 1813 | 1814 | .terminal .xterm-bg-color-201 { 1815 | background-color: #ff00ff; 1816 | } 1817 | 1818 | .terminal .xterm-color-202 { 1819 | color: #ff5f00; 1820 | } 1821 | 1822 | .terminal .xterm-bg-color-202 { 1823 | background-color: #ff5f00; 1824 | } 1825 | 1826 | .terminal .xterm-color-203 { 1827 | color: #ff5f5f; 1828 | } 1829 | 1830 | .terminal .xterm-bg-color-203 { 1831 | background-color: #ff5f5f; 1832 | } 1833 | 1834 | .terminal .xterm-color-204 { 1835 | color: #ff5f87; 1836 | } 1837 | 1838 | .terminal .xterm-bg-color-204 { 1839 | background-color: #ff5f87; 1840 | } 1841 | 1842 | .terminal .xterm-color-205 { 1843 | color: #ff5faf; 1844 | } 1845 | 1846 | .terminal .xterm-bg-color-205 { 1847 | background-color: #ff5faf; 1848 | } 1849 | 1850 | .terminal .xterm-color-206 { 1851 | color: #ff5fd7; 1852 | } 1853 | 1854 | .terminal .xterm-bg-color-206 { 1855 | background-color: #ff5fd7; 1856 | } 1857 | 1858 | .terminal .xterm-color-207 { 1859 | color: #ff5fff; 1860 | } 1861 | 1862 | .terminal .xterm-bg-color-207 { 1863 | background-color: #ff5fff; 1864 | } 1865 | 1866 | .terminal .xterm-color-208 { 1867 | color: #ff8700; 1868 | } 1869 | 1870 | .terminal .xterm-bg-color-208 { 1871 | background-color: #ff8700; 1872 | } 1873 | 1874 | .terminal .xterm-color-209 { 1875 | color: #ff875f; 1876 | } 1877 | 1878 | .terminal .xterm-bg-color-209 { 1879 | background-color: #ff875f; 1880 | } 1881 | 1882 | .terminal .xterm-color-210 { 1883 | color: #ff8787; 1884 | } 1885 | 1886 | .terminal .xterm-bg-color-210 { 1887 | background-color: #ff8787; 1888 | } 1889 | 1890 | .terminal .xterm-color-211 { 1891 | color: #ff87af; 1892 | } 1893 | 1894 | .terminal .xterm-bg-color-211 { 1895 | background-color: #ff87af; 1896 | } 1897 | 1898 | .terminal .xterm-color-212 { 1899 | color: #ff87d7; 1900 | } 1901 | 1902 | .terminal .xterm-bg-color-212 { 1903 | background-color: #ff87d7; 1904 | } 1905 | 1906 | .terminal .xterm-color-213 { 1907 | color: #ff87ff; 1908 | } 1909 | 1910 | .terminal .xterm-bg-color-213 { 1911 | background-color: #ff87ff; 1912 | } 1913 | 1914 | .terminal .xterm-color-214 { 1915 | color: #ffaf00; 1916 | } 1917 | 1918 | .terminal .xterm-bg-color-214 { 1919 | background-color: #ffaf00; 1920 | } 1921 | 1922 | .terminal .xterm-color-215 { 1923 | color: #ffaf5f; 1924 | } 1925 | 1926 | .terminal .xterm-bg-color-215 { 1927 | background-color: #ffaf5f; 1928 | } 1929 | 1930 | .terminal .xterm-color-216 { 1931 | color: #ffaf87; 1932 | } 1933 | 1934 | .terminal .xterm-bg-color-216 { 1935 | background-color: #ffaf87; 1936 | } 1937 | 1938 | .terminal .xterm-color-217 { 1939 | color: #ffafaf; 1940 | } 1941 | 1942 | .terminal .xterm-bg-color-217 { 1943 | background-color: #ffafaf; 1944 | } 1945 | 1946 | .terminal .xterm-color-218 { 1947 | color: #ffafd7; 1948 | } 1949 | 1950 | .terminal .xterm-bg-color-218 { 1951 | background-color: #ffafd7; 1952 | } 1953 | 1954 | .terminal .xterm-color-219 { 1955 | color: #ffafff; 1956 | } 1957 | 1958 | .terminal .xterm-bg-color-219 { 1959 | background-color: #ffafff; 1960 | } 1961 | 1962 | .terminal .xterm-color-220 { 1963 | color: #ffd700; 1964 | } 1965 | 1966 | .terminal .xterm-bg-color-220 { 1967 | background-color: #ffd700; 1968 | } 1969 | 1970 | .terminal .xterm-color-221 { 1971 | color: #ffd75f; 1972 | } 1973 | 1974 | .terminal .xterm-bg-color-221 { 1975 | background-color: #ffd75f; 1976 | } 1977 | 1978 | .terminal .xterm-color-222 { 1979 | color: #ffd787; 1980 | } 1981 | 1982 | .terminal .xterm-bg-color-222 { 1983 | background-color: #ffd787; 1984 | } 1985 | 1986 | .terminal .xterm-color-223 { 1987 | color: #ffd7af; 1988 | } 1989 | 1990 | .terminal .xterm-bg-color-223 { 1991 | background-color: #ffd7af; 1992 | } 1993 | 1994 | .terminal .xterm-color-224 { 1995 | color: #ffd7d7; 1996 | } 1997 | 1998 | .terminal .xterm-bg-color-224 { 1999 | background-color: #ffd7d7; 2000 | } 2001 | 2002 | .terminal .xterm-color-225 { 2003 | color: #ffd7ff; 2004 | } 2005 | 2006 | .terminal .xterm-bg-color-225 { 2007 | background-color: #ffd7ff; 2008 | } 2009 | 2010 | .terminal .xterm-color-226 { 2011 | color: #ffff00; 2012 | } 2013 | 2014 | .terminal .xterm-bg-color-226 { 2015 | background-color: #ffff00; 2016 | } 2017 | 2018 | .terminal .xterm-color-227 { 2019 | color: #ffff5f; 2020 | } 2021 | 2022 | .terminal .xterm-bg-color-227 { 2023 | background-color: #ffff5f; 2024 | } 2025 | 2026 | .terminal .xterm-color-228 { 2027 | color: #ffff87; 2028 | } 2029 | 2030 | .terminal .xterm-bg-color-228 { 2031 | background-color: #ffff87; 2032 | } 2033 | 2034 | .terminal .xterm-color-229 { 2035 | color: #ffffaf; 2036 | } 2037 | 2038 | .terminal .xterm-bg-color-229 { 2039 | background-color: #ffffaf; 2040 | } 2041 | 2042 | .terminal .xterm-color-230 { 2043 | color: #ffffd7; 2044 | } 2045 | 2046 | .terminal .xterm-bg-color-230 { 2047 | background-color: #ffffd7; 2048 | } 2049 | 2050 | .terminal .xterm-color-231 { 2051 | color: #ffffff; 2052 | } 2053 | 2054 | .terminal .xterm-bg-color-231 { 2055 | background-color: #ffffff; 2056 | } 2057 | 2058 | .terminal .xterm-color-232 { 2059 | color: #080808; 2060 | } 2061 | 2062 | .terminal .xterm-bg-color-232 { 2063 | background-color: #080808; 2064 | } 2065 | 2066 | .terminal .xterm-color-233 { 2067 | color: #121212; 2068 | } 2069 | 2070 | .terminal .xterm-bg-color-233 { 2071 | background-color: #121212; 2072 | } 2073 | 2074 | .terminal .xterm-color-234 { 2075 | color: #1c1c1c; 2076 | } 2077 | 2078 | .terminal .xterm-bg-color-234 { 2079 | background-color: #1c1c1c; 2080 | } 2081 | 2082 | .terminal .xterm-color-235 { 2083 | color: #262626; 2084 | } 2085 | 2086 | .terminal .xterm-bg-color-235 { 2087 | background-color: #262626; 2088 | } 2089 | 2090 | .terminal .xterm-color-236 { 2091 | color: #303030; 2092 | } 2093 | 2094 | .terminal .xterm-bg-color-236 { 2095 | background-color: #303030; 2096 | } 2097 | 2098 | .terminal .xterm-color-237 { 2099 | color: #3a3a3a; 2100 | } 2101 | 2102 | .terminal .xterm-bg-color-237 { 2103 | background-color: #3a3a3a; 2104 | } 2105 | 2106 | .terminal .xterm-color-238 { 2107 | color: #444444; 2108 | } 2109 | 2110 | .terminal .xterm-bg-color-238 { 2111 | background-color: #444444; 2112 | } 2113 | 2114 | .terminal .xterm-color-239 { 2115 | color: #4e4e4e; 2116 | } 2117 | 2118 | .terminal .xterm-bg-color-239 { 2119 | background-color: #4e4e4e; 2120 | } 2121 | 2122 | .terminal .xterm-color-240 { 2123 | color: #585858; 2124 | } 2125 | 2126 | .terminal .xterm-bg-color-240 { 2127 | background-color: #585858; 2128 | } 2129 | 2130 | .terminal .xterm-color-241 { 2131 | color: #626262; 2132 | } 2133 | 2134 | .terminal .xterm-bg-color-241 { 2135 | background-color: #626262; 2136 | } 2137 | 2138 | .terminal .xterm-color-242 { 2139 | color: #6c6c6c; 2140 | } 2141 | 2142 | .terminal .xterm-bg-color-242 { 2143 | background-color: #6c6c6c; 2144 | } 2145 | 2146 | .terminal .xterm-color-243 { 2147 | color: #767676; 2148 | } 2149 | 2150 | .terminal .xterm-bg-color-243 { 2151 | background-color: #767676; 2152 | } 2153 | 2154 | .terminal .xterm-color-244 { 2155 | color: #808080; 2156 | } 2157 | 2158 | .terminal .xterm-bg-color-244 { 2159 | background-color: #808080; 2160 | } 2161 | 2162 | .terminal .xterm-color-245 { 2163 | color: #8a8a8a; 2164 | } 2165 | 2166 | .terminal .xterm-bg-color-245 { 2167 | background-color: #8a8a8a; 2168 | } 2169 | 2170 | .terminal .xterm-color-246 { 2171 | color: #949494; 2172 | } 2173 | 2174 | .terminal .xterm-bg-color-246 { 2175 | background-color: #949494; 2176 | } 2177 | 2178 | .terminal .xterm-color-247 { 2179 | color: #9e9e9e; 2180 | } 2181 | 2182 | .terminal .xterm-bg-color-247 { 2183 | background-color: #9e9e9e; 2184 | } 2185 | 2186 | .terminal .xterm-color-248 { 2187 | color: #a8a8a8; 2188 | } 2189 | 2190 | .terminal .xterm-bg-color-248 { 2191 | background-color: #a8a8a8; 2192 | } 2193 | 2194 | .terminal .xterm-color-249 { 2195 | color: #b2b2b2; 2196 | } 2197 | 2198 | .terminal .xterm-bg-color-249 { 2199 | background-color: #b2b2b2; 2200 | } 2201 | 2202 | .terminal .xterm-color-250 { 2203 | color: #bcbcbc; 2204 | } 2205 | 2206 | .terminal .xterm-bg-color-250 { 2207 | background-color: #bcbcbc; 2208 | } 2209 | 2210 | .terminal .xterm-color-251 { 2211 | color: #c6c6c6; 2212 | } 2213 | 2214 | .terminal .xterm-bg-color-251 { 2215 | background-color: #c6c6c6; 2216 | } 2217 | 2218 | .terminal .xterm-color-252 { 2219 | color: #d0d0d0; 2220 | } 2221 | 2222 | .terminal .xterm-bg-color-252 { 2223 | background-color: #d0d0d0; 2224 | } 2225 | 2226 | .terminal .xterm-color-253 { 2227 | color: #dadada; 2228 | } 2229 | 2230 | .terminal .xterm-bg-color-253 { 2231 | background-color: #dadada; 2232 | } 2233 | 2234 | .terminal .xterm-color-254 { 2235 | color: #e4e4e4; 2236 | } 2237 | 2238 | .terminal .xterm-bg-color-254 { 2239 | background-color: #e4e4e4; 2240 | } 2241 | 2242 | .terminal .xterm-color-255 { 2243 | color: #eeeeee; 2244 | } 2245 | 2246 | .terminal .xterm-bg-color-255 { 2247 | background-color: #eeeeee; 2248 | } 2249 | -------------------------------------------------------------------------------- /client/src/script.js: -------------------------------------------------------------------------------- 1 | 2 | function openTerminal() { 3 | var client = new WsptyClient(); 4 | var term = new Terminal(); 5 | var container = document.getElementById('term-container'); 6 | term.open(container); 7 | 8 | 9 | 10 | function debounce(fn) { 11 | // throttle to 4 calls per second, 12 | // call 3 more times after last debounced call 13 | // because zoom seems to be asynchronous in chrome, 14 | // and character measurement returns wrong values for 15 | // a short time 16 | var tid = null, counter = 0; 17 | return function() { 18 | counter = 3; 19 | if (tid === null) { 20 | tid = setInterval(function() { 21 | if (--counter < 1) { 22 | clearTimeout(tid); 23 | tid = null; 24 | } 25 | fn(); 26 | }, 250); 27 | } 28 | }; 29 | } 30 | 31 | function measureChar(subjectRow) { 32 | var contentBuffer = subjectRow.innerHTML; 33 | subjectRow.style.display = 'inline'; 34 | subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace 35 | var characterWidth = subjectRow.getBoundingClientRect().width; 36 | subjectRow.style.display = ''; // Revert style before calculating height, since they differ. 37 | var characterHeight = parseInt(subjectRow.offsetHeight); 38 | subjectRow.innerHTML = contentBuffer; 39 | return { width: characterWidth, height: characterHeight }; 40 | } 41 | 42 | 43 | function fitTerminal() { 44 | var subjectRow = term.rowContainer.firstElementChild; 45 | var charSize = measureChar(subjectRow); 46 | // 10px padding 47 | var ww = window.innerWidth - 10, wh = window.innerHeight - 10; 48 | container.style.width = ww + 'px'; 49 | container.style.height = wh + 'px'; 50 | // 17px scrollbar 51 | var cols = Math.floor((ww - 17) / charSize.width); 52 | var rows = Math.floor(wh / charSize.height); 53 | term.resize(cols, rows); 54 | } 55 | 56 | var debouncedFitTerminal = debounce(function() { 57 | fitTerminal(); 58 | }); 59 | window.addEventListener('resize', function() { 60 | setTimeout(debouncedFitTerminal, 1000); 61 | debouncedFitTerminal(); 62 | }); 63 | fitTerminal(); 64 | 65 | term.on('data', function(data) { 66 | client.send(data); 67 | }); 68 | term.on('resize', function(geom) { 69 | client.resize(geom.cols, geom.rows); 70 | }); 71 | term.write('Connecting...\r\n'); 72 | 73 | var wsProtocol = location.protocol === 'http:' ? 'ws' : 'wss'; 74 | var endpoint = wsProtocol + '://' + location.host + '/wssh' + location.search; 75 | client.connect({ 76 | ws: new WebSocket(endpoint), 77 | onError: function(error) { 78 | term.writeln('Error: ' + error); 79 | }, 80 | onConnect: function() { 81 | // Erase our connecting message 82 | // term.write('\x1b[2K\r'); 83 | client.resize(term.cols, term.rows); 84 | }, 85 | onClose: function() { 86 | term.write('\r\nConnection closed by peer'); 87 | }, 88 | onData: function(data) { 89 | term.write(data); 90 | } 91 | }); 92 | } 93 | 94 | window.addEventListener('load', function() { 95 | openTerminal(); 96 | }); 97 | -------------------------------------------------------------------------------- /client/src/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | padding: 0; 5 | } 6 | 7 | html, body, #term-container { 8 | margin: 0; 9 | overflow: hidden; 10 | background: black; 11 | } 12 | 13 | #term-container { 14 | display: block; 15 | position: relative; 16 | padding: 5px; 17 | } 18 | 19 | /* https://github.com/sourcelair/xterm.js/issues/357 */ 20 | .terminal .xterm-helper-textarea { 21 | top: 0 !important; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/wspty.js: -------------------------------------------------------------------------------- 1 | 2 | function WsptyClient() { 3 | this._connection = null; 4 | }; 5 | 6 | WsptyClient.prototype.connect = function(options) { 7 | this._connection = options.ws; 8 | 9 | this._connection.onopen = function() { 10 | options.onConnect(); 11 | }; 12 | 13 | this._connection.onmessage = function(evt) { 14 | var data = JSON.parse(evt.data); 15 | if (data.error !== undefined) { 16 | options.onError(data.error); 17 | } 18 | if (data.data !== undefined) { 19 | options.onData(data.data); 20 | } 21 | }; 22 | 23 | this._connection.onclose = function(evt) { 24 | options.onClose(); 25 | }; 26 | }; 27 | 28 | WsptyClient.prototype.send = function(data) { 29 | this._connection.send(JSON.stringify({'data': data})); 30 | }; 31 | 32 | WsptyClient.prototype.resize = function(cols, rows) { 33 | this._connection.send(JSON.stringify({'resize': { width: cols, height: rows }})); 34 | }; 35 | -------------------------------------------------------------------------------- /docs/ss_mc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorgloomer/websocket_terminal/42c24391d51c275eabf1f879fb312b9a3614f51e/docs/ss_mc.png -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python3 server-python3/websocket_terminal.py "$@" 3 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | python3 server-python3/websocket_terminal.py %* 2 | -------------------------------------------------------------------------------- /server-python3/config.py: -------------------------------------------------------------------------------- 1 | import sys 2 | DEBUG = False 3 | if '--debug' in sys.argv: 4 | DEBUG = True 5 | sys.argv.remove('--debug') 6 | 7 | CLIENT_DIR = 'src' 8 | -------------------------------------------------------------------------------- /server-python3/logs.py: -------------------------------------------------------------------------------- 1 | def setup(): 2 | import config 3 | import logging 4 | level = logging.INFO 5 | if config.DEBUG: 6 | level = logging.DEBUG 7 | logging.basicConfig(level=level) 8 | -------------------------------------------------------------------------------- /server-python3/requirements.txt: -------------------------------------------------------------------------------- 1 | paramiko 2 | eventlet 3 | flask 4 | -------------------------------------------------------------------------------- /server-python3/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import urllib.parse 3 | 4 | import eventlet 5 | import eventlet.green.socket 6 | # eventlet.monkey_patch() 7 | import eventlet.websocket 8 | import eventlet.wsgi 9 | import wspty.pipe 10 | from flask import Flask, request, redirect 11 | from wspty.EchoTerminal import EchoTerminal 12 | from wspty.EncodedTerminal import EncodedTerminal 13 | from wspty.WebsocketBinding import WebsocketBinding 14 | 15 | import config 16 | 17 | 18 | def make_app(): 19 | app = Flask(__name__) 20 | app.static_folder = get_static_folder() 21 | print("Serving static files from: " + app.static_folder) 22 | 23 | @app.route('/') 24 | def index(): 25 | newurl = b'/static/index.html' 26 | if request.query_string: 27 | newurl = newurl + b'?' + request.query_string 28 | return redirect(newurl) 29 | return app 30 | 31 | 32 | def parse_query(qstr): 33 | return {k: v[0] for k, v in urllib.parse.parse_qs(qstr).items()} 34 | 35 | 36 | def debug(s): 37 | app.logger.debug(s) 38 | 39 | 40 | class TerminalFactory: 41 | def __init__(self, args_dict, allow_unsafe=False): 42 | self.kind = args_dict['kind'] 43 | self.hostname = args_dict.get('hostname', 'localhost') 44 | self.port = int(args_dict.get('port', '22')) 45 | self.username = args_dict.get('username') 46 | self.password = args_dict.get('password') 47 | self.term = args_dict.get('term') 48 | self.encoding = args_dict.get('encoding', 'utf8') 49 | self.allow_unsafe = allow_unsafe 50 | 51 | def create_binary(self): 52 | if self.kind == 'ssh': 53 | from wspty.SshTerminal import SshTerminal 54 | return SshTerminal( 55 | self.hostname, self.port, self.username, self.password, self.term 56 | ) 57 | if self.kind == 'raw': 58 | from wspty.SocketTerminal import SocketTerminal 59 | sock = eventlet.green.socket.socket() 60 | ip = eventlet.green.socket.gethostbyname(self.hostname) 61 | sock.connect((ip, self.port)) 62 | return SocketTerminal(sock) 63 | if self.kind == 'echo': 64 | return EchoTerminal() 65 | if self.kind == 'prompt': 66 | if not self.allow_unsafe: 67 | raise Exception("kind {} is disabled".format(self.kind)) 68 | from wspty import PromptTerminal 69 | return PromptTerminal.os_terminal() 70 | raise NotImplemented('kind: {}'.format(self.kind)) 71 | 72 | def create(self): 73 | return EncodedTerminal(self.create_binary(), self.encoding) 74 | 75 | 76 | class DefaultRootApp: 77 | def __init__(self): 78 | self._app_handle_wssh = eventlet.websocket.WebSocketWSGI(self.handle_wssh) 79 | self.allow_unsafe = False 80 | 81 | def handle_wssh(self, ws): 82 | debug('Creating terminal with remote {remote}'.format( 83 | remote=ws.environ.get('REMOTE_ADDR'), 84 | )) 85 | 86 | ws_binding = WebsocketBinding(ws) 87 | query = parse_query(ws.environ.get('QUERY_STRING', '')) 88 | terminal = None 89 | try: 90 | kind, terminal = self.create_terminal(query) 91 | ws_binding.send('Connected to %s\r\n' % (kind,)) 92 | wspty.pipe.pipe(ws_binding, terminal) 93 | except BaseException as e: 94 | ws_binding.send_error(e) 95 | raise 96 | finally: 97 | if terminal: 98 | terminal.close() 99 | 100 | debug('Closing terminal normally with remote {remote}'.format( 101 | remote=ws.environ.get('REMOTE_ADDR'), 102 | )) 103 | return '' 104 | 105 | def create_terminal(self, obj): 106 | factory = TerminalFactory(obj, self.allow_unsafe) 107 | return factory.kind, factory.create() 108 | 109 | def handler(self, env, *args): 110 | route = env["PATH_INFO"] 111 | if route == '/wssh': 112 | return self._app_handle_wssh(env, *args) 113 | else: 114 | return app(env, *args) 115 | 116 | 117 | def make_parser(): 118 | import argparse 119 | parser = argparse.ArgumentParser(description='Websocket Terminal server') 120 | parser.add_argument('-l', '--listen', default='', help='Listen on interface (default all)') 121 | parser.add_argument('-p', '--port', default=5002, type=int, help='Listen on port') 122 | parser.add_argument('--unsafe', action='store_true', help='Allow unauthenticated connections to local machine') 123 | return parser 124 | 125 | 126 | def start(interface, port, root_app_handler): 127 | conn = (interface, port) 128 | listener = eventlet.listen(conn) 129 | print('listening on {0}:{1}'.format(*conn)) 130 | try: 131 | eventlet.wsgi.server(listener, root_app_handler) 132 | except KeyboardInterrupt: 133 | pass 134 | 135 | 136 | def start_default(interface, port, allow_unsafe=False, root_app_cls=DefaultRootApp): 137 | root_app = root_app_cls() 138 | root_app.allow_unsafe = allow_unsafe 139 | start(interface, port, root_app.handler) 140 | 141 | 142 | def main(): 143 | args = make_parser().parse_args() 144 | start_default(args.listen, args.port, args.unsafe) 145 | 146 | 147 | def get_static_folder(): 148 | path_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../client') 149 | path_root = os.path.join(path_root, config.CLIENT_DIR) 150 | return os.path.abspath(path_root) 151 | 152 | 153 | app = make_app() 154 | 155 | if __name__ == '__main__': 156 | main() 157 | -------------------------------------------------------------------------------- /server-python3/test/test.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import sys 3 | import subprocess 4 | 5 | 6 | import eventlet 7 | import eventlet.tpool 8 | import eventlet.green.subprocess 9 | from eventlet import green 10 | 11 | 12 | eventlet.monkey_patch() 13 | 14 | 15 | def consume(stream, pref=b'T> '): 16 | print("CHK consume 1") 17 | p = pref 18 | while True: 19 | print("CHK consume 2") 20 | data = stream.read(1024) 21 | print("CHK consume 3") 22 | if not data: 23 | break 24 | if p: 25 | data = p + data 26 | p = None 27 | sys.stdout.buffer.write(data.replace(b'\n', b'\n' + pref)) 28 | print("CHK consume 4") 29 | sys.stdout.flush() 30 | print("CHK consume 5") 31 | 32 | 33 | def start_daemon_thread(fn): 34 | thread = threading.Thread(target=fn) 35 | thread.daemon = True 36 | print("CHK start_daemon_thread 1") 37 | thread.start() 38 | print("CHK start_daemon_thread 2") 39 | return thread 40 | 41 | 42 | def consume_input(): 43 | print("CHK consume_input input") 44 | while True: 45 | line = input() + '\n' 46 | print("CHK consume_input line", line) 47 | proc.stdin.write(bytes(line, 'ascii')) 48 | proc.stdin.flush() 49 | 50 | 51 | proc = green.subprocess.Popen("cmd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0) 52 | 53 | 54 | def spawn(fn): 55 | print("CHK spawn") 56 | return start_daemon_thread(fn) 57 | #return eventlet.spawn(fn) 58 | #return eventlet.tpool.execute(fn) 59 | 60 | 61 | thread1 = spawn(lambda: consume(proc.stdout, b"T> ")) 62 | thread2 = spawn(lambda: consume(proc.stderr, b"E> ")) 63 | print("CHK sleeping") 64 | eventlet.sleep(2) 65 | 66 | consume_input() 67 | -------------------------------------------------------------------------------- /server-python3/websocket_terminal.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | import logs 3 | logs.setup() 4 | import server 5 | server.main() 6 | 7 | 8 | if __name__ == '__main__': 9 | main() 10 | -------------------------------------------------------------------------------- /server-python3/wspty/BaseTerminal.py: -------------------------------------------------------------------------------- 1 | class BaseTerminal: 2 | def send(self, msg: bytes): 3 | raise NotImplementedError('send') 4 | 5 | def resize(self, cols: int, rows: int): 6 | pass 7 | 8 | def recv(self, count: int=None): 9 | raise NotImplementedError('recv') 10 | 11 | def close(self): 12 | pass 13 | -------------------------------------------------------------------------------- /server-python3/wspty/EchoTerminal.py: -------------------------------------------------------------------------------- 1 | from .BaseTerminal import BaseTerminal 2 | from eventlet.queue import Queue 3 | 4 | 5 | class EchoTerminal(BaseTerminal): 6 | def __init__(self): 7 | super().__init__() 8 | self._queue = Queue() 9 | 10 | def send(self, data): 11 | self._queue.put(data) 12 | 13 | def recv(self, count=None): 14 | return self._queue.get() 15 | -------------------------------------------------------------------------------- /server-python3/wspty/EncodedTerminal.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | 4 | class EncodedTerminal: 5 | def __init__(self, terminal, encoding='utf8', chunk_size=4096): 6 | self.terminal = terminal 7 | self.encoder = codecs.getincrementalencoder(encoding)() 8 | self.decoder = codecs.getincrementaldecoder(encoding)() 9 | self.encoding = encoding 10 | self.chunk_size = chunk_size 11 | 12 | def write(self, data_str): 13 | return self.terminal.send(self.encoder.encode(data_str)) 14 | 15 | def resize(self, cols, rows): 16 | return self.terminal.resize(cols, rows) 17 | 18 | def read(self): 19 | return self.decoder.decode(self.terminal.recv(self.chunk_size)) 20 | 21 | def close(self): 22 | return self.terminal.close() 23 | -------------------------------------------------------------------------------- /server-python3/wspty/PromptTerminal.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | 4 | import eventlet.queue 5 | import eventlet.tpool 6 | import eventlet.green.subprocess 7 | from eventlet import green 8 | from eventlet.greenpool import GreenPool 9 | 10 | from .BaseTerminal import BaseTerminal 11 | 12 | import logging 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class SubprocessTerminal(BaseTerminal): 17 | def __init__(self, cmd): 18 | self.process = make_simple_process(cmd) 19 | self.queue = eventlet.queue.Queue() 20 | self.greenpool = self._start_consume() 21 | 22 | def _start_consume(self): 23 | greenpool = GreenPool(5) 24 | greenpool.spawn_n(self._consume_stream, self.process.stdout) 25 | greenpool.spawn_n(self._consume_stream, self.process.stderr) 26 | return greenpool 27 | 28 | def _consume_stream(self, stream): 29 | while True: 30 | data = stream.read() 31 | if not data: 32 | break 33 | self._send_to_slave(data) 34 | 35 | def recv(self, count=None): 36 | return self.master_to_slave(self.queue.get()) 37 | 38 | def _send_to_slave(self, data): 39 | self.queue.put(data) 40 | 41 | def send(self, data): 42 | data = self.slave_to_master(data) 43 | self.process.stdin.write(data) 44 | 45 | def slave_to_master(self, x): 46 | return x 47 | 48 | def master_to_slave(self, x): 49 | return x 50 | 51 | def close(self): 52 | self.process.kill() 53 | 54 | 55 | class LinuxTerminal(SubprocessTerminal): 56 | def __init__(self, cmd=None): 57 | if cmd is None: 58 | cmd = ['bash'] 59 | import shlex 60 | cmd = " ".join(map(shlex.quote, cmd)) 61 | cmd = ['script', '-qfc', cmd, '/dev/null'] 62 | super().__init__(cmd) 63 | 64 | 65 | class WindowsTerminal(SubprocessTerminal): 66 | def __init__(self, cmd=None): 67 | if cmd is None: 68 | cmd = ['cmd'] 69 | super().__init__(cmd) 70 | 71 | def slave_to_master(self, data): 72 | data = data.replace(b'\r', b'\r\n') 73 | self._send_to_slave(data) 74 | return data 75 | 76 | def master_to_slave(self, x): 77 | return x.replace(b'\n', b'\r\n') 78 | 79 | 80 | class NonBlockingSimplePipe: 81 | def __init__(self, stream): 82 | logger.debug("NonBlockingSimplePipe.__init__ type(stream) == {}".format(type(stream))) 83 | logger.debug("NonBlockingSimplePipe.__init__ type(stream).__name__ == {!r}".format(type(stream).__name__)) 84 | self.needs_thread = not is_greenpipe(stream) 85 | self.stream = stream 86 | 87 | def read(self): 88 | if self.needs_thread: 89 | return eventlet.tpool.execute(self._read) 90 | return self._read() 91 | 92 | def write(self, data): 93 | if self.needs_thread: 94 | return eventlet.tpool.execute(self._write, data) 95 | return self._write(data) 96 | 97 | def _read(self): 98 | return self.stream.read(2048) 99 | 100 | def _write(self, data): 101 | self.stream.write(data) 102 | self.stream.flush() 103 | 104 | 105 | class NonBlockingSimpleProcess: 106 | def __init__(self, cmd): 107 | self.proc = make_subprocess(cmd) 108 | self.stdin = NonBlockingSimplePipe(self.proc.stdin) 109 | self.stdout = NonBlockingSimplePipe(self.proc.stdout) 110 | self.stderr = NonBlockingSimplePipe(self.proc.stderr) 111 | 112 | def kill(self): 113 | self.proc.kill() 114 | 115 | 116 | def is_greenpipe(obj): 117 | # GreenFileIO is not exposed and GreenPipe is not a class, so checking by name 118 | return type(obj).__name__ == "GreenFileIO" 119 | 120 | 121 | def os_terminal(): 122 | return OS_TERMINALS[sys.platform]() 123 | 124 | 125 | def make_subprocess(obj): 126 | def green_popen(cmd): 127 | p = subprocess.PIPE 128 | return green.subprocess.Popen(cmd, stdin=p, stdout=p, stderr=p, bufsize=0) 129 | if isinstance(obj, str): 130 | return green_popen([obj]) 131 | if isinstance(obj, list): 132 | return green_popen(obj) 133 | if isinstance(obj, subprocess.Popen): 134 | return obj 135 | if isinstance(obj, green.subprocess.Popen): 136 | return obj 137 | raise Exception("Invalid argument to make_subprocess: {}".format(type(obj))) 138 | 139 | 140 | def make_simple_process(obj): 141 | if isinstance(obj, NonBlockingSimpleProcess): 142 | return obj 143 | proc = make_subprocess(obj) 144 | return NonBlockingSimpleProcess(proc) 145 | 146 | 147 | OS_TERMINALS = { 148 | 'linux': LinuxTerminal, 149 | 'win32': WindowsTerminal 150 | } 151 | -------------------------------------------------------------------------------- /server-python3/wspty/SocketTerminal.py: -------------------------------------------------------------------------------- 1 | from .BaseTerminal import BaseTerminal 2 | 3 | 4 | class SocketTerminal(BaseTerminal): 5 | def __init__(self, socket): 6 | super().__init__() 7 | self.socket = socket 8 | 9 | def send(self, data): 10 | return self.socket.sendall(data) 11 | 12 | def recv(self, count=None): 13 | return self.socket.recv(count) 14 | -------------------------------------------------------------------------------- /server-python3/wspty/SshTerminal.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | from .BaseTerminal import BaseTerminal 3 | 4 | 5 | class SshTerminal(BaseTerminal): 6 | def __init__(self, hostname='localhost', port=22, username=None, password=None, term=None): 7 | if term is None: 8 | term = 'xterm' 9 | super().__init__() 10 | self._term = term 11 | self._ssh = paramiko.SSHClient() 12 | self._ssh.set_missing_host_key_policy( 13 | paramiko.AutoAddPolicy()) 14 | self._channel = None 15 | self._hostname = hostname 16 | self._port = port 17 | self._username = username 18 | self._password = password 19 | self._open() 20 | 21 | def _open(self): 22 | self._ssh.connect( 23 | hostname=self._hostname, 24 | port=self._port, 25 | username=self._username, 26 | password=self._password, 27 | look_for_keys=False 28 | ) 29 | self._channel = self._ssh.invoke_shell(self._term) 30 | 31 | def send(self, data): 32 | return self._channel.send(data) 33 | 34 | def recv(self, count=None): 35 | return self._channel.recv(count) 36 | 37 | def close(self): 38 | return self._ssh.close() 39 | 40 | def resize(self, cols, rows): 41 | return self._channel.resize_pty(cols, rows) 42 | -------------------------------------------------------------------------------- /server-python3/wspty/WebsocketBinding.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class DataPacket: 5 | def __init__(self, msg): 6 | self.data = msg.get('data') 7 | resize = msg.get('resize') 8 | if resize is not None: 9 | resize = (int(resize.get('width', 80)), int(resize.get('height', 24))) 10 | self.resize = resize 11 | 12 | 13 | class WebsocketBinding: 14 | def __init__(self, ws): 15 | self.websocket = ws 16 | 17 | def send(self, data_str): 18 | self.websocket.send(json.dumps({'data': str(data_str)})) 19 | 20 | def send_error(self, error_str): 21 | self.websocket.send(json.dumps({'error': str(error_str)})) 22 | 23 | def receive(self): 24 | data = self.websocket.wait() 25 | if data is None: 26 | return None 27 | return DataPacket(json.loads(data)) 28 | 29 | def close(self): 30 | self.websocket.close() 31 | -------------------------------------------------------------------------------- /server-python3/wspty/pipe.py: -------------------------------------------------------------------------------- 1 | import eventlet 2 | import eventlet.event 3 | 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class GreenletRace: 9 | def __init__(self, tasks): 10 | self._event = eventlet.event.Event() 11 | self._tasks = [eventlet.spawn(self._wrap, fn) for fn in tasks] 12 | 13 | def _resolve(self, value=None): 14 | if not self._event.ready(): 15 | self._event.send(value) 16 | 17 | def _reject(self, reason): 18 | if not self._event.ready(): 19 | self._event.send_exception(reason) 20 | 21 | def _wrap(self, fn): 22 | try: 23 | self._resolve(fn()) 24 | except BaseException as e: 25 | self._reject(e) 26 | 27 | def wait(self): 28 | self._event.wait() 29 | 30 | def kill_all(self): 31 | for greenthread in self._tasks: 32 | greenthread.kill() 33 | 34 | 35 | class TerminalPipe: 36 | def __init__(self, ws_binding, terminal): 37 | self.ws_binding = ws_binding 38 | self.terminal = terminal 39 | self._tasks = None 40 | 41 | def pipe(self): 42 | try: 43 | self._tasks = GreenletRace([ 44 | self._pty_to_ws, 45 | self._ws_to_pty 46 | ]) 47 | self._tasks.wait() 48 | finally: 49 | self.close() 50 | 51 | def _pty_to_ws(self): 52 | while True: 53 | data = self.terminal.read() 54 | if not data: 55 | break 56 | self.ws_binding.send(data) 57 | 58 | def _ws_to_pty(self): 59 | while True: 60 | msg = self.ws_binding.receive() 61 | if not msg: 62 | break 63 | if msg.resize is not None: 64 | self.terminal.resize(*msg.resize) 65 | if msg.data is not None: 66 | self.terminal.write(msg.data) 67 | 68 | def close(self): 69 | if self._tasks is not None: 70 | self._tasks.kill_all() 71 | 72 | 73 | def pipe(ws_binding, terminal): 74 | TerminalPipe(ws_binding, terminal).pipe() 75 | -------------------------------------------------------------------------------- /setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pip3 install -r server-python3/requirements.txt 3 | -------------------------------------------------------------------------------- /setup.bat: -------------------------------------------------------------------------------- 1 | pip3 install -r server-python3/requirements.txt 2 | --------------------------------------------------------------------------------