mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-16 00:25:48 +08:00
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run
S_HTTPSRV, the type of admin and httpsrv services, was added before S_ZOMBIE without a name in the string table, so its index held "ZOMBIE": the admin services page, %N in logs and RADIUS named admin and httpsrv services and their clients ZOMBIE, and a zombie service had no name at all (NULL). Index 21 is now "HTTPSRV", 22 "ZOMBIE"; 22 was unused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
"""The admin interface, now a set of handlers on the HTTP server."""
|
|
|
|
import time
|
|
|
|
|
|
def run(t):
|
|
adm = t.free_port()
|
|
lim = t.free_port()
|
|
server = t.start("admin", f"""
|
|
log
|
|
auth iponly
|
|
allow *
|
|
countin 1 D 100 * * *
|
|
countin 2 D 200 * * *
|
|
admin -p{adm}
|
|
|
|
flush
|
|
auth iponly
|
|
allow *
|
|
admin -p{lim} -s1
|
|
""", ports=[adm, lim])
|
|
|
|
url = f"http://127.0.0.1:{adm}"
|
|
|
|
def reload(path):
|
|
"""Ask for a reload and wait for it to be done. It happens on the next
|
|
tick of the main loop, at most a second later; both old services stop
|
|
and the new ones start, and a request in between is refused (Windows
|
|
cannot bind a port still listening) or reset."""
|
|
before = server.output()
|
|
accepting = before.count("Accepting connections")
|
|
exiting = before.count("Exiting thread")
|
|
reply = t.http(url + path)
|
|
deadline = time.time() + 10
|
|
while time.time() < deadline:
|
|
now = server.output()
|
|
if (now.count("Accepting connections") >= accepting + 2
|
|
and now.count("Exiting thread") >= exiting + 2):
|
|
break
|
|
time.sleep(0.05)
|
|
return reply
|
|
|
|
# --- the predefined pages -----------------------------------------
|
|
t.eq(200, t.http(url + "/").status, "the main page")
|
|
t.eq(200, t.http(url + "/C").status, "the counters page")
|
|
t.eq(200, reload("/R").status, "the reload page")
|
|
t.eq(200, t.http(url + "/S").status, "the services page")
|
|
|
|
counters = t.http(url + "/C")
|
|
t.contains(counters, "countin", "the counters page names the counter type")
|
|
t.contains(counters, "<tr>", "the counters page renders a table")
|
|
t.contains(reload("/R"), "Reload", "the reload page confirms the request")
|
|
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
|
# admin and httpsrv services are of type HTTPSRV, named as such, not as
|
|
# the zombie a stopped service is
|
|
services = t.http(url + "/S")
|
|
t.contains(services, "CDATA[HTTPSRV]", "the services page names the admin service HTTPSRV")
|
|
t.not_contains(services, "CDATA[ZOMBIE]", "and no running service a zombie")
|
|
|
|
# --- the menu no longer offers the removed config editor -----------
|
|
main = t.http(url + "/")
|
|
t.contains(main, "HREF='/C'", "the menu links to the counters")
|
|
t.contains(main, "HREF='/R'", "the menu links to reload")
|
|
t.contains(main, "HREF='/S'", "the menu links to the services")
|
|
t.not_contains(main, "HREF='/F'",
|
|
"the menu no longer links to the config editor")
|
|
|
|
# /F and /U are gone, so they fall through to the catch-all rule
|
|
t.eq(200, t.http(url + "/F").status, "the removed /F falls through")
|
|
t.eq(200, t.http(url + "/U").status, "the removed /U falls through")
|
|
t.contains(t.http(url + "/F"), "configuration", "/F yields the main page")
|
|
|
|
# --- counter control through the glob ------------------------------
|
|
# /C<action><number> is routed by the /C* rule, the action arriving as
|
|
# the glob
|
|
t.http(url + "/CD0")
|
|
t.contains(t.http(url + "/C"), ">NO<", "a counter can be disabled")
|
|
t.http(url + "/CS0")
|
|
t.contains(t.http(url + "/C"), ">YES<", "a counter can be enabled again")
|
|
|
|
# --- limited mode ---------------------------------------------------
|
|
limited = f"http://127.0.0.1:{lim}"
|
|
t.eq(200, t.http(limited + "/").status, "limited mode serves the main page")
|
|
t.eq(200, t.http(limited + "/C").status, "limited mode serves the counters")
|
|
t.not_contains(t.http(limited + "/R"), "Reload scheduled",
|
|
"limited mode refuses a reload")
|
|
|
|
# --- the writable command is gone ------------------------------------
|
|
output = t.run_config("writable", f"""
|
|
log
|
|
writable
|
|
admin -p{t.free_port()}
|
|
""")
|
|
t.contains(output, "Unknown command", "the writable command is rejected")
|