mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Added more darktheme
- Added more dark theme css - Merged main branch fixes and new features - Added todo tag for custom timeout
This commit is contained in:
parent
4cf5d29692
commit
ec5c24b9b8
@ -308,7 +308,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
}
|
}
|
||||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||||
//This cert is expired
|
//This cert is expired
|
||||||
|
|
||||||
DNSName, err := ExtractDomains(certBytes)
|
DNSName, err := ExtractDomains(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Maybe self signed. Ignore this
|
//Maybe self signed. Ignore this
|
||||||
|
@ -109,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
|
|||||||
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
||||||
thisTransporter.(*http.Transport).DisableCompression = true
|
thisTransporter.(*http.Transport).DisableCompression = true
|
||||||
|
|
||||||
|
//TODO: Add user adjustable timeout option here
|
||||||
|
|
||||||
if dpcOptions.IgnoreTLSVerification {
|
if dpcOptions.IgnoreTLSVerification {
|
||||||
//Ignore TLS certificate validation error
|
//Ignore TLS certificate validation error
|
||||||
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
||||||
|
@ -50,21 +50,6 @@ func NewSSHProxyManager() *Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next free port in the list
|
|
||||||
func (m *Manager) GetNextPort() int {
|
|
||||||
nextPort := m.StartingPort
|
|
||||||
occupiedPort := make(map[int]bool)
|
|
||||||
for _, instance := range m.Instances {
|
|
||||||
occupiedPort[instance.AssignedPort] = true
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if !occupiedPort[nextPort] {
|
|
||||||
return nextPort
|
|
||||||
}
|
|
||||||
nextPort++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
|
func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
|
||||||
targetInstance, err := m.GetInstanceById(instanceId)
|
targetInstance, err := m.GetInstanceById(instanceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -168,6 +153,17 @@ func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIp
|
|||||||
if username != "" {
|
if username != "" {
|
||||||
connAddr = username + "@" + remoteIpAddr
|
connAddr = username + "@" + remoteIpAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Trim the space in the username and remote address
|
||||||
|
username = strings.TrimSpace(username)
|
||||||
|
remoteIpAddr = strings.TrimSpace(remoteIpAddr)
|
||||||
|
|
||||||
|
//Validate the username and remote address
|
||||||
|
err := ValidateUsernameAndRemoteAddr(username, remoteIpAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty")
|
configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty")
|
||||||
title := username + "@" + remoteIpAddr
|
title := username + "@" + remoteIpAddr
|
||||||
if remotePort != 22 {
|
if remotePort != 22 {
|
||||||
|
66
src/mod/sshprox/sshprox_test.go
Normal file
66
src/mod/sshprox/sshprox_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package sshprox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstance_Destroy(t *testing.T) {
|
||||||
|
manager := NewSSHProxyManager()
|
||||||
|
instance, err := manager.NewSSHProxy("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new SSH proxy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.Destroy()
|
||||||
|
|
||||||
|
if len(manager.Instances) != 0 {
|
||||||
|
t.Errorf("Expected Instances to be empty, got %d", len(manager.Instances))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstance_ValidateUsernameAndRemoteAddr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
username string
|
||||||
|
remoteAddr string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"validuser", "127.0.0.1", false},
|
||||||
|
{"valid.user", "example.com", false},
|
||||||
|
{"; bash ;", "example.com", true},
|
||||||
|
{"valid-user", "example.com", false},
|
||||||
|
{"invalid user", "127.0.0.1", true},
|
||||||
|
{"validuser", "invalid address", true},
|
||||||
|
{"invalid@user", "127.0.0.1", true},
|
||||||
|
{"validuser", "invalid@address", true},
|
||||||
|
{"injection; rm -rf /", "127.0.0.1", true},
|
||||||
|
{"validuser", "127.0.0.1; rm -rf /", true},
|
||||||
|
{"$(reboot)", "127.0.0.1", true},
|
||||||
|
{"validuser", "$(reboot)", true},
|
||||||
|
{"validuser", "127.0.0.1; $(reboot)", true},
|
||||||
|
{"validuser", "127.0.0.1 | ls", true},
|
||||||
|
{"validuser", "127.0.0.1 & ls", true},
|
||||||
|
{"validuser", "127.0.0.1 && ls", true},
|
||||||
|
{"validuser", "127.0.0.1 |& ls", true},
|
||||||
|
{"validuser", "127.0.0.1 ; ls", true},
|
||||||
|
{"validuser", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", false},
|
||||||
|
{"validuser", "2001:db8::ff00:42:8329", false},
|
||||||
|
{"validuser", "2001:db8:0:1234:0:567:8:1", false},
|
||||||
|
{"validuser", "2001:db8::1234:0:567:8:1", false},
|
||||||
|
{"validuser", "2001:db8:0:0:0:0:2:1", false},
|
||||||
|
{"validuser", "2001:db8::2:1", false},
|
||||||
|
{"validuser", "2001:db8:0:0:8:800:200c:417a", false},
|
||||||
|
{"validuser", "2001:db8::8:800:200c:417a", false},
|
||||||
|
{"validuser", "2001:db8:0:0:8:800:200c:417a; rm -rf /", true},
|
||||||
|
{"validuser", "2001:db8::8:800:200c:417a; rm -rf /", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
err := ValidateUsernameAndRemoteAddr(test.username, test.remoteAddr)
|
||||||
|
if test.expectError && err == nil {
|
||||||
|
t.Errorf("Expected error for username %s and remoteAddr %s, but got none", test.username, test.remoteAddr)
|
||||||
|
}
|
||||||
|
if !test.expectError && err != nil {
|
||||||
|
t.Errorf("Did not expect error for username %s and remoteAddr %s, but got %v", test.username, test.remoteAddr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package sshprox
|
package sshprox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -34,6 +36,21 @@ func IsWebSSHSupported() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the next free port in the list
|
||||||
|
func (m *Manager) GetNextPort() int {
|
||||||
|
nextPort := m.StartingPort
|
||||||
|
occupiedPort := make(map[int]bool)
|
||||||
|
for _, instance := range m.Instances {
|
||||||
|
occupiedPort[instance.AssignedPort] = true
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if !occupiedPort[nextPort] {
|
||||||
|
return nextPort
|
||||||
|
}
|
||||||
|
nextPort++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a given domain and port is a valid ssh server
|
// Check if a given domain and port is a valid ssh server
|
||||||
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||||
timeout := time.Second * 3
|
timeout := time.Second * 3
|
||||||
@ -60,13 +77,47 @@ func IsSSHConnectable(ipOrDomain string, port int) bool {
|
|||||||
return string(buf[:7]) == "SSH-2.0"
|
return string(buf[:7]) == "SSH-2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the port is used by other process or application
|
// Validate the username and remote address to prevent injection
|
||||||
func isPortInUse(port int) bool {
|
func ValidateUsernameAndRemoteAddr(username string, remoteIpAddr string) error {
|
||||||
address := fmt.Sprintf(":%d", port)
|
// Validate and sanitize the username to prevent ssh injection
|
||||||
listener, err := net.Listen("tcp", address)
|
validUsername := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||||
if err != nil {
|
if !validUsername.MatchString(username) {
|
||||||
|
return errors.New("invalid username, only alphanumeric characters, dots, underscores and dashes are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the remoteIpAddr is a valid ipv4 or ipv6 address
|
||||||
|
if net.ParseIP(remoteIpAddr) != nil {
|
||||||
|
//A valid IP address do not need further validation
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and sanitize the remote domain to prevent injection
|
||||||
|
validRemoteAddr := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||||
|
if !validRemoteAddr.MatchString(remoteIpAddr) {
|
||||||
|
return errors.New("invalid remote address, only alphanumeric characters, dots, underscores and dashes are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the given ip or domain is a loopback address
|
||||||
|
// or resolves to a loopback address
|
||||||
|
func IsLoopbackIPOrDomain(ipOrDomain string) bool {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(ipOrDomain), "localhost") || strings.TrimSpace(ipOrDomain) == "127.0.0.1" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
listener.Close()
|
|
||||||
|
//Check if the ipOrDomain resolves to a loopback address
|
||||||
|
ips, err := net.LookupIP(ipOrDomain)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||||
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
|
<button class="ui basic button" onclick="uploadPrivateKey();"><i class="grey lock icon"></i> Private Key</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
@ -144,11 +144,7 @@ body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{
|
|||||||
background-color: var(--theme_bg) !important;
|
background-color: var(--theme_bg) !important;
|
||||||
color: var(--text_color) !important;
|
color: var(--text_color) !important;
|
||||||
border: 1px solid var(--divider_color) !important;
|
border: 1px solid var(--divider_color) !important;
|
||||||
}
|
}s
|
||||||
|
|
||||||
.toobar #mainmenu a.item:hover{
|
|
||||||
background-color: var(--theme_highlight) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.darkTheme .ui.segment:not(.basic) {
|
body.darkTheme .ui.segment:not(.basic) {
|
||||||
background-color: var(--theme_bg) !important;
|
background-color: var(--theme_bg) !important;
|
||||||
@ -156,6 +152,12 @@ body.darkTheme .ui.segment:not(.basic) {
|
|||||||
border: 1px solid transparent !important;
|
border: 1px solid transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.darkTheme .ui.segment{
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: var(--text_color) !important;
|
||||||
|
border: 1px solid transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
body.darkTheme .sub.header {
|
body.darkTheme .sub.header {
|
||||||
color: var(--text_color) !important;
|
color: var(--text_color) !important;
|
||||||
}
|
}
|
||||||
@ -565,7 +567,7 @@ body.darkTheme .ui.selection.fluid.dropdown#accessRuleSelector .dropdown.icon {
|
|||||||
/* Tab Menu in access control */
|
/* Tab Menu in access control */
|
||||||
|
|
||||||
body.darkTheme .ui.top.attached.tabular.menu {
|
body.darkTheme .ui.top.attached.tabular.menu {
|
||||||
background-color: var(--theme_bg) !important;
|
background-color: transparent !important;
|
||||||
color: var(--text_color) !important;
|
color: var(--text_color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +581,7 @@ body.darkTheme .ui.top.attached.tabular.menu .item:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.darkTheme .ui.top.attached.tabular.menu .item.active {
|
body.darkTheme .ui.top.attached.tabular.menu .item.active {
|
||||||
background-color: var(--theme_bg) !important;
|
background-color: var(--theme_bg_primary) !important;
|
||||||
color: var(--text_color) !important;
|
color: var(--text_color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,3 +708,48 @@ body.darkTheme .ui.selection.dropdown#defaultCA .dropdown.icon {
|
|||||||
color: var(--text_color) !important;
|
color: var(--text_color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
ZeroTier
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
body.darkTheme #gan .ui.list .item .icon {
|
||||||
|
color: var(--icon_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #gan .ui.list .item .content .header {
|
||||||
|
color: var(--text_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #gan .ui.list .item .content .description {
|
||||||
|
color: var(--text_color_secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #gan .clickable.iprange.active {
|
||||||
|
background-color: var(--theme_highlight) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #gan thead th {
|
||||||
|
background-color: var(--theme_bg_secondary) !important;
|
||||||
|
color: var(--text_color) !important;
|
||||||
|
border-color: var(--divider_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Uptime Monitor
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
body.darkTheme #utm .standardContainer {
|
||||||
|
background-color: var(--theme_bg) !important;
|
||||||
|
color: var(--text_color) !important;
|
||||||
|
border: 1px solid var(--divider_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #utm .standardContainer .padding.statusDot {
|
||||||
|
background-color: var(--theme_bg) !important;
|
||||||
|
border: 0.2px solid var(--theme_bg_primary) !important;
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
@ -10,6 +10,19 @@
|
|||||||
<body>
|
<body>
|
||||||
<br />
|
<br />
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="showUnexposed" class="hidden" />
|
||||||
|
<label for="showUnexposed"
|
||||||
|
>Show Containers with Unexposed Ports
|
||||||
|
<br />
|
||||||
|
<small
|
||||||
|
>Please make sure Zoraxy and the target container share a
|
||||||
|
network</small
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="ui header">
|
<div class="ui header">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
List of Docker Containers
|
List of Docker Containers
|
||||||
@ -33,56 +46,70 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const lines = {};
|
let lines = {};
|
||||||
const linesAded = {};
|
let linesAdded = {};
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("showUnexposed")
|
||||||
|
.addEventListener("change", () => {
|
||||||
|
console.log("showUnexposed", $("#showUnexposed").is(":checked"));
|
||||||
|
$("#containersList").html('<div class="ui loader active"></div>');
|
||||||
|
|
||||||
|
$("#containersAddedList").empty();
|
||||||
|
$("#containersAddedListHeader").attr("hidden", true);
|
||||||
|
|
||||||
|
lines = {};
|
||||||
|
linesAdded = {};
|
||||||
|
|
||||||
|
getDockerContainers();
|
||||||
|
});
|
||||||
|
|
||||||
function getDockerContainers() {
|
function getDockerContainers() {
|
||||||
const hostRequest = $.get("/api/proxy/list?type=host");
|
const hostRequest = $.get("/api/proxy/list?type=host");
|
||||||
const dockerRequest = $.get("/api/docker/containers");
|
const dockerRequest = $.get("/api/docker/containers");
|
||||||
|
|
||||||
// Wait for both requests to complete
|
|
||||||
Promise.all([hostRequest, dockerRequest])
|
Promise.all([hostRequest, dockerRequest])
|
||||||
.then(([hostData, dockerData]) => {
|
.then(([hostData, dockerData]) => {
|
||||||
if (
|
if (!dockerData.error && !hostData.error) {
|
||||||
dockerData.error === undefined &&
|
|
||||||
hostData.error === undefined
|
|
||||||
) {
|
|
||||||
const { containers, network } = dockerData;
|
const { containers, network } = dockerData;
|
||||||
const bridge = network.find(({ Name }) => Name === "bridge");
|
|
||||||
const {
|
const existingTargets = new Set(
|
||||||
IPAM: {
|
hostData.flatMap(({ ActiveOrigins }) =>
|
||||||
Config: [{ Gateway: gateway }],
|
ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
|
||||||
},
|
)
|
||||||
} = bridge;
|
);
|
||||||
const existedDomains = hostData.reduce((acc, { ActiveOrigins }) => {
|
|
||||||
return acc.concat(ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
const {
|
const Ports = container.Ports;
|
||||||
Ports,
|
const name = container.Names[0].replace(/^\//, "");
|
||||||
Names: [name],
|
|
||||||
} = container;
|
|
||||||
|
|
||||||
for (const portObject of Ports.filter(
|
for (const portObject of Ports) {
|
||||||
({ IP: ip }) => ip === "::" || ip === '0.0.0.0'
|
let port = portObject.PublicPort;
|
||||||
)) {
|
if (!port) {
|
||||||
const { IP: ip, PublicPort: port } = portObject;
|
if (!$("#showUnexposed").is(":checked")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
port = portObject.PrivatePort;
|
||||||
|
}
|
||||||
const key = `${name}-${port}`;
|
const key = `${name}-${port}`;
|
||||||
|
|
||||||
|
// if port is not exposed, use container's name and let docker handle the routing
|
||||||
|
// BUT this will only work if the container is on the same network as Zoraxy
|
||||||
|
const targetAddress = portObject.IP || name;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
existedDomains.some((item) => item === `${gateway}:${port}`) &&
|
existingTargets.has(`${targetAddress}:${port}`) &&
|
||||||
!linesAded[key]
|
!linesAdded[key]
|
||||||
) {
|
) {
|
||||||
linesAded[key] = {
|
linesAdded[key] = {
|
||||||
name: name.replace(/^\//, ""),
|
name,
|
||||||
ip: gateway,
|
ip: targetAddress,
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
} else if (!lines[key]) {
|
} else if (!lines[key]) {
|
||||||
lines[key] = {
|
lines[key] = {
|
||||||
name: name.replace(/^\//, ""),
|
name,
|
||||||
ip: gateway,
|
ip: targetAddress,
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -92,29 +119,31 @@
|
|||||||
for (const [key, line] of Object.entries(lines)) {
|
for (const [key, line] of Object.entries(lines)) {
|
||||||
$("#containersList").append(
|
$("#containersList").append(
|
||||||
`<div class="item">
|
`<div class="item">
|
||||||
<div class="right floated content">
|
<div class="right floated content">
|
||||||
<div class="ui button" onclick="addContainerItem('${key}');">Add</div>
|
<div class="ui button" onclick="addContainerItem('${key}');">Add</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">${line.name}</div>
|
<div class="header">${line.name}</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
${line.ip}:${line.port}
|
${line.ip}:${line.port}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const [key, line] of Object.entries(linesAded)) {
|
|
||||||
|
for (const [key, line] of Object.entries(linesAdded)) {
|
||||||
$("#containersAddedList").append(
|
$("#containersAddedList").append(
|
||||||
`<div class="item">
|
`<div class="item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">${line.name}</div>
|
<div class="header">${line.name}</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
${line.ip}:${line.port}
|
${line.ip}:${line.port}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Object.entries(linesAded).length &&
|
|
||||||
|
Object.entries(linesAdded).length &&
|
||||||
$("#containersAddedListHeader").removeAttr("hidden");
|
$("#containersAddedListHeader").removeAttr("hidden");
|
||||||
$("#containersList .loader").removeClass("active");
|
$("#containersList .loader").removeClass("active");
|
||||||
} else {
|
} else {
|
||||||
@ -122,7 +151,11 @@
|
|||||||
`Error loading data: ${dockerData.error || hostData.error}`,
|
`Error loading data: ${dockerData.error || hostData.error}`,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
$("#containersList").html(`<div class="ui basic segment"><i class="ui red times icon"></i> ${dockerData.error || hostData.error}</div>`);
|
$("#containersList").html(
|
||||||
|
`<div class="ui basic segment"><i class="ui red times icon"></i> ${
|
||||||
|
dockerData.error || hostData.error
|
||||||
|
}</div>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -42,7 +42,7 @@ func HandleCreateProxySession(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if !*allowSshLoopback {
|
if !*allowSshLoopback {
|
||||||
//Not allow loopback connections
|
//Not allow loopback connections
|
||||||
if strings.EqualFold(strings.TrimSpace(ipaddr), "localhost") || strings.TrimSpace(ipaddr) == "127.0.0.1" {
|
if sshprox.IsLoopbackIPOrDomain(ipaddr) {
|
||||||
//Request target is loopback
|
//Request target is loopback
|
||||||
utils.SendErrorResponse(w, "loopback web ssh connection is not enabled on this host")
|
utils.SendErrorResponse(w, "loopback web ssh connection is not enabled on this host")
|
||||||
return
|
return
|
||||||
@ -74,7 +74,7 @@ func HandleCreateProxySession(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if the host support ssh, or if the target domain (and port, optional) support ssh
|
// Check if the host support ssh, or if the target domain (and port, optional) support ssh
|
||||||
func HandleWebSshSupportCheck(w http.ResponseWriter, r *http.Request) {
|
func HandleWebSshSupportCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
domain, err := utils.PostPara(r, "domain")
|
domain, err := utils.PostPara(r, "domain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user