Fix Onlines
This commit is contained in:
@@ -167,12 +167,20 @@ chmod 755 "$BIN"
|
|||||||
USER_NAME="admin"
|
USER_NAME="admin"
|
||||||
PASSWORD="$(rand_hex 10)"
|
PASSWORD="$(rand_hex 10)"
|
||||||
TOKEN="$(rand_hex 24)"
|
TOKEN="$(rand_hex 24)"
|
||||||
|
PANEL_URL=""
|
||||||
|
PANEL_SERVER_ID="0"
|
||||||
|
PANEL_SERVER_IP=""
|
||||||
|
PANEL_PUSH_INTERVAL="60"
|
||||||
|
|
||||||
if [ -f "$CONFIG_DIR/config.json" ]; then
|
if [ -f "$CONFIG_DIR/config.json" ]; then
|
||||||
echo "Existing config found: $CONFIG_DIR/config.json"
|
echo "Existing config found: $CONFIG_DIR/config.json"
|
||||||
USER_NAME="$(grep -o '"username"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo admin)"
|
USER_NAME="$(grep -o '"username"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo admin)"
|
||||||
PASSWORD="$(grep -o '"password"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 10)"
|
PASSWORD="$(grep -o '"password"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 10)"
|
||||||
TOKEN="$(grep -o '"token"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 24)"
|
TOKEN="$(grep -o '"token"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 24)"
|
||||||
|
PANEL_URL="$(grep -o '"panel_url"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo '')"
|
||||||
|
PANEL_SERVER_ID="$(grep -o '"panel_server_id"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_DIR/config.json" | head -1 | grep -o '[0-9]*$' || echo 0)"
|
||||||
|
PANEL_SERVER_IP="$(grep -o '"panel_server_ip"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo '')"
|
||||||
|
PANEL_PUSH_INTERVAL="$(grep -o '"panel_push_interval_seconds"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_DIR/config.json" | head -1 | grep -o '[0-9]*$' || echo 60)"
|
||||||
EXISTING_PORT="$(extract_listen_port "$CONFIG_DIR/config.json" || true)"
|
EXISTING_PORT="$(extract_listen_port "$CONFIG_DIR/config.json" || true)"
|
||||||
if [ "$PORT_SET" = "0" ] && [ -n "$EXISTING_PORT" ]; then
|
if [ "$PORT_SET" = "0" ] && [ -n "$EXISTING_PORT" ]; then
|
||||||
PORT="$EXISTING_PORT"
|
PORT="$EXISTING_PORT"
|
||||||
@@ -191,7 +199,11 @@ cat > "$CONFIG_DIR/config.json" <<EOF
|
|||||||
"username": "$USER_NAME",
|
"username": "$USER_NAME",
|
||||||
"password": "$PASSWORD",
|
"password": "$PASSWORD",
|
||||||
"token": "$TOKEN",
|
"token": "$TOKEN",
|
||||||
"data_dir": "$CONFIG_DIR"
|
"data_dir": "$CONFIG_DIR",
|
||||||
|
"panel_url": "$PANEL_URL",
|
||||||
|
"panel_server_id": $PANEL_SERVER_ID,
|
||||||
|
"panel_server_ip": "$PANEL_SERVER_IP",
|
||||||
|
"panel_push_interval_seconds": $PANEL_PUSH_INTERVAL
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
chmod 600 "$CONFIG_DIR/config.json"
|
chmod 600 "$CONFIG_DIR/config.json"
|
||||||
|
|||||||
273
main.go
273
main.go
@@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -24,11 +26,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Listen string `json:"listen"`
|
Listen string `json:"listen"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
DataDir string `json:"data_dir"`
|
DataDir string `json:"data_dir"`
|
||||||
|
PanelURL string `json:"panel_url"`
|
||||||
|
PanelServerID int `json:"panel_server_id"`
|
||||||
|
PanelServerIP string `json:"panel_server_ip"`
|
||||||
|
PanelPushIntervalSeconds int `json:"panel_push_interval_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
@@ -48,11 +54,14 @@ type Store struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
sessions map[string]time.Time
|
configPath string
|
||||||
sessMu sync.Mutex
|
sessions map[string]time.Time
|
||||||
store *Store
|
sessMu sync.Mutex
|
||||||
startedAt time.Time
|
store *Store
|
||||||
|
startedAt time.Time
|
||||||
|
panelMu sync.Mutex
|
||||||
|
lastPush time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
var usernameRE = regexp.MustCompile(`^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,31}$`)
|
var usernameRE = regexp.MustCompile(`^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,31}$`)
|
||||||
@@ -77,6 +86,9 @@ func main() {
|
|||||||
if cfg.Password == "" || cfg.Token == "" {
|
if cfg.Password == "" || cfg.Token == "" {
|
||||||
log.Fatalf("username/password/token must be configured in %s", *cfgPath)
|
log.Fatalf("username/password/token must be configured in %s", *cfgPath)
|
||||||
}
|
}
|
||||||
|
if cfg.PanelPushIntervalSeconds <= 0 {
|
||||||
|
cfg.PanelPushIntervalSeconds = 60
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(cfg.DataDir, 0700); err != nil {
|
if err := os.MkdirAll(cfg.DataDir, 0700); err != nil {
|
||||||
log.Fatalf("data dir: %v", err)
|
log.Fatalf("data dir: %v", err)
|
||||||
@@ -86,14 +98,16 @@ func main() {
|
|||||||
log.Fatalf("store: %v", err)
|
log.Fatalf("store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &App{cfg: cfg, sessions: map[string]time.Time{}, store: st, startedAt: time.Now()}
|
app := &App{cfg: cfg, configPath: *cfgPath, sessions: map[string]time.Time{}, store: st, startedAt: time.Now()}
|
||||||
go app.expiryLoop()
|
go app.expiryLoop()
|
||||||
|
go app.onlinePushLoop()
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", app.handleLegacyCommand)
|
mux.HandleFunc("/", app.handleLegacyCommand)
|
||||||
mux.HandleFunc("/api/auth/login", app.handleLogin)
|
mux.HandleFunc("/api/auth/login", app.handleLogin)
|
||||||
mux.Handle("/api/auth/me", app.auth(http.HandlerFunc(app.handleMe)))
|
mux.Handle("/api/auth/me", app.auth(http.HandlerFunc(app.handleMe)))
|
||||||
mux.Handle("/api/users", app.auth(http.HandlerFunc(app.handleUsers)))
|
mux.Handle("/api/users", app.auth(http.HandlerFunc(app.handleUsers)))
|
||||||
|
mux.Handle("/api/onlines", app.auth(http.HandlerFunc(app.handleOnlines)))
|
||||||
mux.Handle("/api/users/create", app.auth(http.HandlerFunc(app.handleCreateUser)))
|
mux.Handle("/api/users/create", app.auth(http.HandlerFunc(app.handleCreateUser)))
|
||||||
mux.Handle("/api/users/delete", app.auth(http.HandlerFunc(app.handleDeleteUser)))
|
mux.Handle("/api/users/delete", app.auth(http.HandlerFunc(app.handleDeleteUser)))
|
||||||
mux.Handle("/api/dragonpanel/create", app.auth(http.HandlerFunc(app.handleDragonCreate)))
|
mux.Handle("/api/dragonpanel/create", app.auth(http.HandlerFunc(app.handleDragonCreate)))
|
||||||
@@ -392,6 +406,7 @@ func (a *App) auth(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
for _, tok := range staticTokens {
|
for _, tok := range staticTokens {
|
||||||
if strings.TrimSpace(tok) != "" && strings.TrimSpace(tok) == a.cfg.Token {
|
if strings.TrimSpace(tok) != "" && strings.TrimSpace(tok) == a.cfg.Token {
|
||||||
|
a.learnPanelFromRequest(r)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -414,6 +429,7 @@ func (a *App) auth(next http.Handler) http.Handler {
|
|||||||
errText(w, 401, "unauthorized")
|
errText(w, 401, "unauthorized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
a.learnPanelFromRequest(r)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -431,6 +447,234 @@ func (a *App) handleUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSON(w, users)
|
writeJSON(w, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) handleOnlines(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
counts := collectOnlineCounts()
|
||||||
|
users := make([]map[string]interface{}, 0, len(counts))
|
||||||
|
total := 0
|
||||||
|
for username, count := range counts {
|
||||||
|
if count <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
users = append(users, map[string]interface{}{"username": username, "usuario": username, "quantidade": count, "active_conns": count})
|
||||||
|
total += count
|
||||||
|
}
|
||||||
|
sort.Slice(users, func(i, j int) bool { return fmt.Sprint(users[i]["username"]) < fmt.Sprint(users[j]["username"]) })
|
||||||
|
writeJSON(w, map[string]interface{}{"ok": true, "total": total, "users": users})
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePanelURL(raw string) string {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(strings.ToLower(raw), "http://") && !strings.HasPrefix(strings.ToLower(raw), "https://") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimRight(raw, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) learnPanelFromRequest(r *http.Request) {
|
||||||
|
panelURL := normalizePanelURL(r.Header.Get("X-Dragon-Panel-Url"))
|
||||||
|
serverIP := strings.TrimSpace(r.Header.Get("X-Dragon-Panel-Server-Ip"))
|
||||||
|
serverID, _ := strconv.Atoi(strings.TrimSpace(r.Header.Get("X-Dragon-Panel-Server-Id")))
|
||||||
|
if panelURL == "" && serverID <= 0 && serverIP == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.panelMu.Lock()
|
||||||
|
changed := false
|
||||||
|
if panelURL != "" && panelURL != a.cfg.PanelURL {
|
||||||
|
a.cfg.PanelURL = panelURL
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if serverID > 0 && serverID != a.cfg.PanelServerID {
|
||||||
|
a.cfg.PanelServerID = serverID
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if serverIP != "" && serverIP != a.cfg.PanelServerIP {
|
||||||
|
a.cfg.PanelServerIP = serverIP
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
cfgCopy := a.cfg
|
||||||
|
a.panelMu.Unlock()
|
||||||
|
if changed {
|
||||||
|
log.Printf("panel online push target set to %s server_id=%d server_ip=%s", cfgCopy.PanelURL, cfgCopy.PanelServerID, cfgCopy.PanelServerIP)
|
||||||
|
a.saveConfig(cfgCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) saveConfig(cfg Config) {
|
||||||
|
if strings.TrimSpace(a.configPath) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(a.configPath, append(b, '\n'), 0600); err != nil {
|
||||||
|
log.Printf("saving bridge config failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) onlinePushLoop() {
|
||||||
|
initialDelay := 10 * time.Second
|
||||||
|
time.Sleep(initialDelay)
|
||||||
|
for {
|
||||||
|
interval := a.cfg.PanelPushIntervalSeconds
|
||||||
|
if interval <= 0 {
|
||||||
|
interval = 60
|
||||||
|
}
|
||||||
|
a.pushOnlineSnapshot()
|
||||||
|
time.Sleep(time.Duration(interval) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) pushOnlineSnapshot() {
|
||||||
|
a.panelMu.Lock()
|
||||||
|
panelURL := normalizePanelURL(a.cfg.PanelURL)
|
||||||
|
serverID := a.cfg.PanelServerID
|
||||||
|
serverIP := a.cfg.PanelServerIP
|
||||||
|
a.panelMu.Unlock()
|
||||||
|
if panelURL == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
counts := collectOnlineCounts()
|
||||||
|
users := make([]map[string]interface{}, 0, len(counts))
|
||||||
|
total := 0
|
||||||
|
for username, count := range counts {
|
||||||
|
if count <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
users = append(users, map[string]interface{}{"usuario": username, "username": username, "quantidade": count})
|
||||||
|
total += count
|
||||||
|
}
|
||||||
|
sort.Slice(users, func(i, j int) bool { return fmt.Sprint(users[i]["usuario"]) < fmt.Sprint(users[j]["usuario"]) })
|
||||||
|
body := map[string]interface{}{
|
||||||
|
"server_id": serverID,
|
||||||
|
"server_ip": serverIP,
|
||||||
|
"hostname": hostname(),
|
||||||
|
"total": total,
|
||||||
|
"onlines": users,
|
||||||
|
"sent_at": time.Now().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(body)
|
||||||
|
endpoint := panelURL + "/api/bridge_onlines.php"
|
||||||
|
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
// The panel validates this against the same bridge/module password/token that
|
||||||
|
// was registered in the server form.
|
||||||
|
req.Header.Set("Senha", a.cfg.Password)
|
||||||
|
req.Header.Set("X-API-Token", a.cfg.Token)
|
||||||
|
req.Header.Set("X-Bridge-Password", a.cfg.Password)
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("online push failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
raw, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
log.Printf("online push rejected: http=%d body=%s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.panelMu.Lock()
|
||||||
|
a.lastPush = time.Now()
|
||||||
|
a.panelMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostname() string {
|
||||||
|
h, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectOnlineCounts() map[string]int {
|
||||||
|
counts := map[string]int{}
|
||||||
|
commands := []string{
|
||||||
|
`ps -ef | grep -oP "sshd: \K\w+(?= \[priv\])" || true`,
|
||||||
|
`sed '/^10.8.0./d' /etc/openvpn/openvpn-status.log 2>/dev/null | grep 127.0.0.1 | awk -F',' '{print $1}' || true`,
|
||||||
|
`printf 'status\n' | nc -q0 127.0.0.1 7505 2>/dev/null | grep -oP '.*?,\K.*?(?=,)' | sort | uniq | grep -v ':' || true`,
|
||||||
|
`awk -v date="$(date -d '60 seconds ago' +'%Y/%m/%d %H:%M:%S')" '$0 > date && /email:/ { sub(/.*email: /, "", $0); sub(/@gmail\.com$/, "", $0); if (!seen[$0]++) print }' /var/log/v2ray/access.log 2>/dev/null || true`,
|
||||||
|
}
|
||||||
|
for _, cmd := range commands {
|
||||||
|
for _, line := range runShellLines(cmd, 8*time.Second) {
|
||||||
|
user := sanitizeOnlineUser(line)
|
||||||
|
if user == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
counts[user]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
}
|
||||||
|
|
||||||
|
func runShellLines(command string, timeout time.Duration) []string {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if timeout > 0 {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
cmd = exec.CommandContext(ctx, "bash", "-lc", command)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("bash", "-lc", command)
|
||||||
|
}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil && len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return splitLines(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitLines(s string) []string {
|
||||||
|
parts := strings.Split(s, "\n")
|
||||||
|
out := make([]string, 0, len(parts))
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p != "" {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeOnlineUser(line string) string {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
badFragments := []string{
|
||||||
|
"No such file or directory",
|
||||||
|
"nc: port number invalid",
|
||||||
|
"UNDEF",
|
||||||
|
"unknown",
|
||||||
|
}
|
||||||
|
lower := strings.ToLower(line)
|
||||||
|
if lower == "root" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, bad := range badFragments {
|
||||||
|
if strings.Contains(line, bad) || strings.Contains(lower, strings.ToLower(bad)) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(line, " \t,;|&<>$`\\\"'") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(line) > 64 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) {
|
func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
@@ -852,11 +1096,12 @@ func (a *App) expiryLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listSystemUsers(st *Store) []map[string]interface{} {
|
func listSystemUsers(st *Store) []map[string]interface{} {
|
||||||
|
counts := collectOnlineCounts()
|
||||||
st.mu.Lock()
|
st.mu.Lock()
|
||||||
defer st.mu.Unlock()
|
defer st.mu.Unlock()
|
||||||
out := make([]map[string]interface{}, 0, len(st.Accounts))
|
out := make([]map[string]interface{}, 0, len(st.Accounts))
|
||||||
for _, ac := range st.Accounts {
|
for _, ac := range st.Accounts {
|
||||||
active := activeProcCount(ac.Username)
|
active := counts[ac.Username]
|
||||||
out = append(out, map[string]interface{}{
|
out = append(out, map[string]interface{}{
|
||||||
"username": ac.Username,
|
"username": ac.Username,
|
||||||
"active_conns": active,
|
"active_conns": active,
|
||||||
@@ -864,14 +1109,14 @@ func listSystemUsers(st *Store) []map[string]interface{} {
|
|||||||
"max_connections": ac.MaxConnections,
|
"max_connections": ac.MaxConnections,
|
||||||
"expires_at": ac.ExpiresAt,
|
"expires_at": ac.ExpiresAt,
|
||||||
"uuid": ac.UUID,
|
"uuid": ac.UUID,
|
||||||
"online_source": "openssh",
|
"online_source": "bridge-local",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Slice(out, func(i, j int) bool { return fmt.Sprint(out[i]["username"]) < fmt.Sprint(out[j]["username"]) })
|
sort.Slice(out, func(i, j int) bool { return fmt.Sprint(out[i]["username"]) < fmt.Sprint(out[j]["username"]) })
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
func activeProcCount(username string) int {
|
func activeProcCount(username string) int {
|
||||||
return activeSSHConnectionCount(username)
|
return collectOnlineCounts()[username]
|
||||||
}
|
}
|
||||||
|
|
||||||
func dragonCoreInstalled() bool {
|
func dragonCoreInstalled() bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user