Merge branch 'webmin:master' into patch-1

This commit is contained in:
7Adrian
2022-10-21 13:09:25 +02:00
committed by GitHub
16 changed files with 348 additions and 50 deletions

View File

@@ -35,7 +35,7 @@ my @extra_reverse = split(/\s+/, $config{'extra_reverse'} || '');
our %is_extra = map { $_, 1 } (@extra_forward, @extra_reverse);
our %access = &get_module_acl();
my $zone_names_cache = "$module_config_directory/zone-names";
my $zone_names_version = 3;
my $zone_names_version = 4;
my @list_zone_names_cache;
my $slave_error;
my %lines_count;
@@ -2196,7 +2196,7 @@ return undef;
sub before_editing
{
my ($zone) = @_;
if (!$freeze_zone_count{$zone->{'name'}}) {
if ($zone->{'dynamic'} && !$freeze_zone_count{$zone->{'name'}}) {
my ($out, $ok) = &try_cmd(
"freeze ".quotemeta($zone->{'name'})." IN ".
quotemeta($zone->{'view'} || ""));
@@ -2226,19 +2226,21 @@ sub restart_zone
{
my ($dom, $view) = @_;
my ($out, $ex);
my $zone = &get_zone_name($dom, $view);
my $dyn = $zone && $zone->{'dynamic'};
if ($view) {
# Reload a zone in a view
&try_cmd("freeze ".quotemeta($dom)." IN ".quotemeta($view));
&try_cmd("freeze ".quotemeta($dom)." IN ".quotemeta($view)) if ($dyn);
$out = &try_cmd("reload ".quotemeta($dom)." IN ".quotemeta($view));
$ex = $?;
&try_cmd("thaw ".quotemeta($dom)." IN ".quotemeta($view));
&try_cmd("thaw ".quotemeta($dom)." IN ".quotemeta($view)) if ($dyn);
}
else {
# Just reload one top-level zone
&try_cmd("freeze ".quotemeta($dom));
&try_cmd("freeze ".quotemeta($dom)) if ($dyn);
$out = &try_cmd("reload ".quotemeta($dom));
$ex = $?;
&try_cmd("thaw ".quotemeta($dom));
&try_cmd("thaw ".quotemeta($dom)) if ($dyn);
}
if ($out =~ /not found/i) {
# Zone is not known to BIND yet - do a total reload
@@ -2457,8 +2459,11 @@ if ($changed || !$znc{'version'} ||
my $type = &find_value("type", $z->{'members'});
next if (!$type);
my $file = &find_value("file", $z->{'members'});
my $up = &find("update-policy", $z->{'members'});
my $au = &find("allow-update", $z->{'members'});
my $dynamic = $up || $au ? 1 : 0;
$znc{"zone_".($n++)} = join("\t", $z->{'value'},
$z->{'index'}, $type, $v->{'value'}, $file);
$z->{'index'}, $type, $v->{'value'}, $dynamic, $file);
$files{$z->{'file'}}++;
}
$znc{"view_".($n++)} = join("\t", $v->{'value'}, $v->{'index'});
@@ -2469,8 +2474,11 @@ if ($changed || !$znc{'version'} ||
next if (!$type);
my $file = &find_value("file", $z->{'members'});
$file ||= ""; # slaves and other types with no file
my $up = &find("update-policy", $z->{'members'});
my $au = &find("allow-update", $z->{'members'});
my $dynamic = $up || $au ? 1 : 0;
$znc{"zone_".($n++)} = join("\t", $z->{'value'},
$z->{'index'}, $type, "*", $file);
$z->{'index'}, $type, "*", $dynamic, $file);
$files{$z->{'file'}}++;
}
@@ -2501,12 +2509,13 @@ if (scalar(@list_zone_names_cache)) {
my (@rv, %viewidx);
foreach my $k (keys %znc) {
if ($k =~ /^zone_(\d+)$/) {
my ($name, $index, $type, $view, $file) =
split(/\t+/, $znc{$k}, 5);
my ($name, $index, $type, $view, $dynamic, $file) =
split(/\t+/, $znc{$k}, 6);
push(@rv, { 'name' => $name,
'type' => $type,
'index' => $index,
'view' => !$view || $view eq '*' ? undef : $view,
'dynamic' => $dynamic,
'file' => $file });
}
elsif ($k =~ /^view_(\d+)$/) {
@@ -2534,7 +2543,7 @@ undef(@list_zone_names_cache);
unlink($zone_names_cache);
}
# get_zone_name(index|name, [viewindex|"any"])
# get_zone_name(index|name, [viewindex|view-name|"any"])
# Returns a zone cache object, looked up by name or index
sub get_zone_name
{
@@ -2546,7 +2555,8 @@ foreach my $z (@zones) {
if ($z->{$field} eq $key &&
($viewidx eq 'any' ||
$viewidx eq '' && !defined($z->{'viewindex'}) ||
$viewidx ne '' && $z->{'viewindex'} == $_[1])) {
$viewidx =~ /^\d+$/ && $z->{'viewindex'} == $viewidx ||
$viewidx ne '' && $z->{'view'} eq $viewidx)) {
return $z;
}
}

View File

@@ -2173,33 +2173,35 @@ my @templates = grep { /\@$/ || /\@\.service$/ } @units;
# Dump state of all of them, 100 at a time
my %info;
my $ecount = 0;
my $out;
my @units_parts;
push @units_parts, [ splice @units, 0, 100 ] while @units;
foreach my $units_part (@units_parts) {
my $cmd;
foreach my $unit (@{$units_part}) {
$cmd .=
"systemctl show --property=Id,Description,UnitFileState,ActiveState,SubState,ExecStart,ExecStop,ExecReload,ExecMainPID,FragmentPath $unit 2>/dev/null ; ";
while(@units) {
my @args;
while(@args < 100 && @units) {
push(@args, shift(@units));
}
# Run combine command for speed
$out .= &backquote_command($cmd);
$ecount++ if ($?);
}
if ($out) {
$out = &backquote_command("systemctl show --property=Id,Description,UnitFileState,ActiveState,SubState,ExecStart,ExecStop,ExecReload,ExecMainPID,FragmentPath ".join(" ", @args)." 2>/dev/null");
my @lines = split(/\r?\n/, $out);
my $curr;
my @units;
if (@lines) {
$curr = { };
push(@units, $curr);
}
foreach my $l (@lines) {
my ($n, $v) = split(/=/, $l, 2);
next if (!$n);
if (lc($n) eq 'id') {
$curr = $v;
$info{$curr} ||= { };
if ($l eq "") {
# Start of a new unit section
$curr = { };
push(@units, $curr);
}
if ($curr) {
$info{$curr}->{$n} = $v;
else {
# A property in the current one
my ($n, $v) = split(/=/, $l, 2);
$curr->{$n} = $v;
}
}
foreach my $u (@units) {
$info{$u->{'Id'}} = $u if ($u->{'Id'});
}
$ecount++ if ($?);
}
if ($ecount && keys(%info) < 2) {
&error("Failed to read systemd units : $out");

View File

@@ -346,7 +346,7 @@ else {
$gid++;
}
$rv = $ldap->add($newdn, attr =>
[ "cn" => $group,
[ "cn" => $grp,
"gidNumber" => $gid,
@props,
"objectClass" => \@classes ] );

View File

@@ -5623,6 +5623,15 @@ if (!$key) {
&http_error(500, "Missing Sec-Websocket-Key header");
return 0;
}
my @users = split(/\s+/, $ws->{'user'});
my @busers = split(/\s+/, $ws->{'buser'});
if (@users || @busers) {
if (&indexof($authuser, @users) < 0 &&
&indexof($baseauthuser, @busers) < 0) {
&http_error(500, "Invalid user for Websockets connection");
return 0;
}
}
my @protos = split(/\s*,\s*/, $header{'sec-websocket-protocol'});
print DEBUG "websockets protos ",join(" ", @protos),"\n";
@@ -5640,23 +5649,6 @@ elsif ($ws->{'pipe'}) {
&http_error(500, "Websockets pipe failed : $?");
print DEBUG "websockets pipe $ws->{'pipe'}\n";
}
elsif ($ws->{'cmd'}) {
# Backend is a shell command
eval "use IO::Pty";
$@ && &http_error(500,"Websockets command requires the IO::Pty module");
my $ptyfh = new IO::Pty;
my $ttyfh = $ptyfh->slave();
my $tty = $ptyfh->ttyname();
# XXX chown tty?
my $pid = fork();
if (!$pid) {
# Run command in a forked process
# XXX todo here
exit(1);
}
$ptyfh->close_slave();
print DEBUG "websockets command $ws->{'cmd'}\n";
}
else {
&http_error(500, "Invalid Webmin websockets config");
}

1
xterm/config Normal file
View File

@@ -0,0 +1 @@
base_port=555

1
xterm/config.info Normal file
View File

@@ -0,0 +1 @@
base_port=Base port number for Websockets connections,0

1
xterm/defaultacl Normal file
View File

@@ -0,0 +1 @@
user=root

65
xterm/index.cgi Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/local/bin/perl
# Show a terminal that is connected to a Websockets server via Webmin proxying
BEGIN { push(@INC, ".."); };
use WebminCore;
&init_config();
my %access = &get_module_acl();
&ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1, 0, undef,
"<link rel=stylesheet href=xterm.css>\n".
"<script src=xterm.js></script>\n".
"<script src=xterm-addon-attach.js></script>\n"
);
# Check for needed modules
my $modname = "Net::WebSocket::Server";
eval "use ${modname};";
if ($@) {
print &text('index_cpan', "<tt>$modname</tt>",
"../cpan/download.cgi?source=3&cpan=$modname&mode=2&return=/$module_name/&returndesc=".&urlize($module_info{'desc'})),"<p>\n";
&ui_print_footer("/", $text{'index'});
return;
}
# Pick a port and configure Webmin to proxy it
my $port = $config{'base_port'} || 555;
while(1) {
&open_socket("127.0.0.1", $port, TEST, \$err);
last if ($err);
close(TEST);
$port++;
}
my %miniserv;
&get_miniserv_config(\%miniserv);
my $wspath = "/$module_name/ws-".$port;
$miniserv{'websockets_'.$wspath} = "host=127.0.0.1 port=$port wspath=/ user=$remote_user";
&put_miniserv_config(\%miniserv);
&reload_miniserv();
# Launch the shell server on that port
&foreign_require("cron");
my $shellserver_cmd = "$module_config_directory/shellserver.pl";
if (!-r $shellserver_cmd) {
&cron::create_wrapper($shellserver_cmd, $module_name, "shellserver.pl");
}
my $user = $access{'user'};
my $tmpdir = &tempname_dir();
&system_logged("$shellserver_cmd $port $user >$tmpdir/ws-$port.out 2>&1 </dev/null &");
sleep(2);
# Open the terminal
my $url = "wss://".$ENV{'HTTP_HOST'}.$wspath;
print <<EOF;
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
var socket = new WebSocket('$url', 'binary');
var attachAddon = new AttachAddon.AttachAddon(socket);
term.loadAddon(attachAddon);
</script>
EOF
&ui_print_footer("/", $text{'index'});

2
xterm/lang/en Normal file
View File

@@ -0,0 +1,2 @@
index_title=Websockets Shell
index_cpan=The Perl module <tt>$1</tt> needed to accept Websockets connections is not available, but you can install it automatically using Webmin's <a href='$2'>Perl Modules</a> module.

3
xterm/module.info Normal file
View File

@@ -0,0 +1,3 @@
desc=Websockets Shell
name=xterm
longdesc=Access the shell on your system without the need for a separate SSH client, using XTerm over Websockets

35
xterm/shellserver.pl Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/local/bin/perl
# Start a websocket server connected to a shell
BEGIN { push(@INC, ".."); };
use WebminCore;
use Net::WebSocket::Server;
&init_config();
my ($port, $user) = @ARGV;
if ($user ne "root" && $<) {
my @uinfo = getpwnam($user);
@uinfo || die "User $user does not exist!";
&switch_to_unix_user(@uinfo);
}
$SIG{'ALRM'} = sub { die "timeout waiting for connection"; };
alarm(60);
print STDERR "listening on port $port\n";
Net::WebSocket::Server->new(
listen => $port,
on_connect => sub {
my ($serv, $conn) = @_;
print STDERR "got connection\n";
alarm(0);
$conn->on(
utf8 => sub {
my ($conn, $msg) = @_;
$conn->send_utf8($msg);
},
disconnect => sub {
exit(0);
},
);
},
)->start;

View File

@@ -0,0 +1,2 @@
!function(s,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.AttachAddon=t():s.AttachAddon=t()}(self,(function(){return(()=>{"use strict";var s={};return(()=>{var t=s;function e(s,t,e){return s.addEventListener(t,e),{dispose:()=>{e&&s.removeEventListener(t,e)}}}Object.defineProperty(t,"__esModule",{value:!0}),t.AttachAddon=void 0,t.AttachAddon=class{constructor(s,t){this._disposables=[],this._socket=s,this._socket.binaryType="arraybuffer",this._bidirectional=!(t&&!1===t.bidirectional)}activate(s){this._disposables.push(e(this._socket,"message",(t=>{const e=t.data;s.write("string"==typeof e?e:new Uint8Array(e))}))),this._bidirectional&&(this._disposables.push(s.onData((s=>this._sendData(s)))),this._disposables.push(s.onBinary((s=>this._sendBinary(s))))),this._disposables.push(e(this._socket,"close",(()=>this.dispose()))),this._disposables.push(e(this._socket,"error",(()=>this.dispose())))}dispose(){for(const s of this._disposables)s.dispose()}_sendData(s){1===this._socket.readyState&&this._socket.send(s)}_sendBinary(s){if(1!==this._socket.readyState)return;const t=new Uint8Array(s.length);for(let e=0;e<s.length;++e)t[e]=255&s.charCodeAt(e);this._socket.send(t)}}})(),s})()}));
//# sourceMappingURL=xterm-addon-attach.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"xterm-addon-attach.js","mappings":"CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAqB,YAAID,IAEzBD,EAAkB,YAAIC,GACvB,CATD,CASGK,MAAM,WACT,M,gDC2DA,SAASC,EAAqDC,EAAmBC,EAASC,GAExF,OADAF,EAAOG,iBAAiBF,EAAMC,GACvB,CACLE,QAAS,KACFF,GAILF,EAAOK,oBAAoBJ,EAAMC,EAAQ,EAG/C,C,sEAnEA,oBAKEI,YAAYN,EAAmBO,GAFvB,KAAAC,aAA8B,GAGpCC,KAAKC,QAAUV,EAEfS,KAAKC,QAAQC,WAAa,cAC1BF,KAAKG,iBAAmBL,IAAqC,IAA1BA,EAAQM,cAC7C,CAEOC,SAASC,GACdN,KAAKD,aAAaQ,KAChBjB,EAAkBU,KAAKC,QAAS,WAAWO,IACzC,MAAMC,EAA6BD,EAAGC,KACtCH,EAASI,MAAsB,iBAATD,EAAoBA,EAAO,IAAIE,WAAWF,GAAM,KAItET,KAAKG,iBACPH,KAAKD,aAAaQ,KAAKD,EAASM,QAAOH,GAAQT,KAAKa,UAAUJ,MAC9DT,KAAKD,aAAaQ,KAAKD,EAASQ,UAASL,GAAQT,KAAKe,YAAYN,OAGpET,KAAKD,aAAaQ,KAAKjB,EAAkBU,KAAKC,QAAS,SAAS,IAAMD,KAAKL,aAC3EK,KAAKD,aAAaQ,KAAKjB,EAAkBU,KAAKC,QAAS,SAAS,IAAMD,KAAKL,YAC7E,CAEOA,UACL,IAAK,MAAMqB,KAAKhB,KAAKD,aACnBiB,EAAErB,SAEN,CAEQkB,UAAUJ,GAGgB,IAA5BT,KAAKC,QAAQgB,YAGjBjB,KAAKC,QAAQiB,KAAKT,EACpB,CAEQM,YAAYN,GAClB,GAAgC,IAA5BT,KAAKC,QAAQgB,WACf,OAEF,MAAME,EAAS,IAAIR,WAAWF,EAAKW,QACnC,IAAK,IAAIC,EAAI,EAAGA,EAAIZ,EAAKW,SAAUC,EACjCF,EAAOE,GAA0B,IAArBZ,EAAKa,WAAWD,GAE9BrB,KAAKC,QAAQiB,KAAKC,EACpB,E","sources":["webpack://AttachAddon/webpack/universalModuleDefinition","webpack://AttachAddon/./src/AttachAddon.ts"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"AttachAddon\"] = factory();\n\telse\n\t\troot[\"AttachAddon\"] = factory();\n})(self, function() {\nreturn ","/**\n * Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Implements the attach method, that attaches the terminal to a WebSocket stream.\n */\n\nimport { Terminal, IDisposable, ITerminalAddon } from 'xterm';\n\ninterface IAttachOptions {\n bidirectional?: boolean;\n}\n\nexport class AttachAddon implements ITerminalAddon {\n private _socket: WebSocket;\n private _bidirectional: boolean;\n private _disposables: IDisposable[] = [];\n\n constructor(socket: WebSocket, options?: IAttachOptions) {\n this._socket = socket;\n // always set binary type to arraybuffer, we do not handle blobs\n this._socket.binaryType = 'arraybuffer';\n this._bidirectional = !(options && options.bidirectional === false);\n }\n\n public activate(terminal: Terminal): void {\n this._disposables.push(\n addSocketListener(this._socket, 'message', ev => {\n const data: ArrayBuffer | string = ev.data;\n terminal.write(typeof data === 'string' ? data : new Uint8Array(data));\n })\n );\n\n if (this._bidirectional) {\n this._disposables.push(terminal.onData(data => this._sendData(data)));\n this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));\n }\n\n this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));\n this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));\n }\n\n public dispose(): void {\n for (const d of this._disposables) {\n d.dispose();\n }\n }\n\n private _sendData(data: string): void {\n // TODO: do something better than just swallowing\n // the data if the socket is not in a working condition\n if (this._socket.readyState !== 1) {\n return;\n }\n this._socket.send(data);\n }\n\n private _sendBinary(data: string): void {\n if (this._socket.readyState !== 1) {\n return;\n }\n const buffer = new Uint8Array(data.length);\n for (let i = 0; i < data.length; ++i) {\n buffer[i] = data.charCodeAt(i) & 255;\n }\n this._socket.send(buffer);\n }\n}\n\nfunction addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {\n socket.addEventListener(type, handler);\n return {\n dispose: () => {\n if (!handler) {\n // Already disposed\n return;\n }\n socket.removeEventListener(type, handler);\n }\n };\n}\n"],"names":["root","factory","exports","module","define","amd","self","addSocketListener","socket","type","handler","addEventListener","dispose","removeEventListener","constructor","options","_disposables","this","_socket","binaryType","_bidirectional","bidirectional","activate","terminal","push","ev","data","write","Uint8Array","onData","_sendData","onBinary","_sendBinary","d","readyState","send","buffer","length","i","charCodeAt"],"sourceRoot":""}

180
xterm/xterm.css Normal file
View File

@@ -0,0 +1,180 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}

2
xterm/xterm.js Normal file

File diff suppressed because one or more lines are too long

1
xterm/xterm.js.map Normal file

File diff suppressed because one or more lines are too long