Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,7 +5,8 @@ import re
|
|
| 5 |
from flask import Flask, render_template_string, request, session, redirect, url_for, send_file, Response
|
| 6 |
|
| 7 |
app = Flask(__name__)
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
# --- IDENTITY & CREDENTIALS ---
|
| 11 |
OWNER_NAME = "WhyLaugh404"
|
|
@@ -24,52 +25,46 @@ ACCESS_LEVELS = {
|
|
| 24 |
"owner": ["sherlock", "maigret", "socialscan", "holehe", "phoneinfoga", "twint", "exiftool", "ghunt", "cloud_enum"]
|
| 25 |
}
|
| 26 |
|
| 27 |
-
# --- REGEX UNTUK BERSIHIN KODE WARNA TERMINAL ---
|
| 28 |
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
| 29 |
|
| 30 |
-
# --- STYLING: J.S.T
|
| 31 |
CSS = """
|
| 32 |
:root { --bg: #000000; --panel: #050505; --accent: #00ff00; --accent-glow: #00ff0044; --text: #00ff00; --border: #111; --danger: #ff003c; }
|
| 33 |
-
body { background: var(--bg); color: var(--text); font-family: 'JetBrains Mono',
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
::-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
:
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
.
|
| 46 |
-
.
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
.
|
| 50 |
-
.
|
| 51 |
-
.grid {
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
.card
|
| 55 |
-
|
| 56 |
-
input { background: transparent; border: 1px solid #333; color: var(--text); padding: 12px; width: 100%; box-sizing: border-box;
|
| 57 |
-
|
| 58 |
-
button { background: transparent; border: 1px solid var(--accent); color: var(--accent); padding: 10px; width: 100%; cursor: pointer; font-weight: bold; letter-spacing: 2px; transition: 0.2s; }
|
| 59 |
button:hover:not(:disabled) { background: var(--accent); color: #000; }
|
| 60 |
-
button:disabled { border-color: #333; color: #333; cursor: not-allowed; text-shadow: none; }
|
| 61 |
|
| 62 |
.console-box { background: #000; border: 1px solid #222; margin-top: 15px; display: none; flex-direction: column; }
|
| 63 |
-
.console-header { background: #050505; padding: 8px
|
| 64 |
-
.console-body { padding:
|
| 65 |
-
.dl-btn { color: var(--accent); text-decoration: none; border: 1px solid var(--accent); padding: 2px
|
| 66 |
-
.dl-btn:hover { background: var(--accent); color: #000; }
|
| 67 |
.blinking-cursor { animation: blink 1s step-end infinite; }
|
| 68 |
@keyframes blink { 50% { opacity: 0; } }
|
| 69 |
"""
|
| 70 |
|
| 71 |
-
# --- SERVER LOGIC ---
|
| 72 |
-
|
| 73 |
@app.route('/', methods=['GET', 'POST'])
|
| 74 |
def entry():
|
| 75 |
if request.method == 'POST':
|
|
@@ -78,21 +73,18 @@ def entry():
|
|
| 78 |
if p == val:
|
| 79 |
session['role'] = role
|
| 80 |
return redirect(url_for('entry'))
|
| 81 |
-
return "<body style='background:#000; color:red; text-align:center; padding-top:10vh; font-family:monospace;
|
| 82 |
|
| 83 |
if 'role' in session:
|
| 84 |
return render_template_string(T_DASHBOARD, css=CSS, owner=OWNER_NAME, team=TEAM_NAME,
|
| 85 |
role=session['role'], allowed=ACCESS_LEVELS[session['role']], mrx_url=MRX_LOGO)
|
| 86 |
-
|
| 87 |
return render_template_string(T_LOGIN, css=CSS, team=TEAM_NAME, owner=OWNER_NAME)
|
| 88 |
|
| 89 |
@app.route('/stream/<tool_id>')
|
| 90 |
def stream(tool_id):
|
| 91 |
if 'role' not in session or tool_id not in ACCESS_LEVELS[session['role']]:
|
| 92 |
return Response("Unauthorized", status=403)
|
| 93 |
-
|
| 94 |
target = request.args.get('target', '').strip()
|
| 95 |
-
|
| 96 |
def generate():
|
| 97 |
cmds = {
|
| 98 |
"sherlock": f"sherlock {target} --timeout 5",
|
|
@@ -105,55 +97,44 @@ def stream(tool_id):
|
|
| 105 |
"ghunt": f"ghunt email {target}",
|
| 106 |
"cloud_enum": f"python3 /home/user/cloud_enum/cloud_enum.py -k {target}"
|
| 107 |
}
|
| 108 |
-
|
| 109 |
cmd = cmds.get(tool_id)
|
| 110 |
report_file = f"JST_REPORT_{tool_id.upper()}_{target.replace('.', '_')}.txt"
|
| 111 |
-
|
| 112 |
-
# FIX: Pisahin string biar Python 3.10 gak ngambek kena backslash di dalem f-string json
|
| 113 |
-
init_msg = f"> Initiating protocol: {tool_id.upper()}\n> Target: {target}\n> Bypassing nodes...\n\n"
|
| 114 |
yield f"data: {json.dumps({'line': init_msg})}\n\n"
|
| 115 |
-
|
| 116 |
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True)
|
| 117 |
-
|
| 118 |
full_log = ""
|
| 119 |
for line in process.stdout:
|
| 120 |
-
# BERSIHKAN KODE WARNA ANSI
|
| 121 |
clean_line = ansi_escape.sub('', line)
|
| 122 |
full_log += clean_line
|
| 123 |
yield f"data: {json.dumps({'line': clean_line})}\n\n"
|
| 124 |
-
|
| 125 |
process.wait()
|
| 126 |
-
|
| 127 |
-
# FIX: Gunakan \n biasa buat write ke file biar turun baris beneran di TXT
|
| 128 |
with open(report_file, "w") as f:
|
| 129 |
-
f.write(f"--- {TEAM_NAME}
|
| 130 |
-
|
| 131 |
yield f"data: {json.dumps({'done': True, 'file': report_file})}\n\n"
|
| 132 |
-
|
| 133 |
return Response(generate(), mimetype='text/event-stream')
|
| 134 |
|
| 135 |
@app.route('/dl/<filename>')
|
| 136 |
def dl(filename):
|
| 137 |
if filename.startswith("JST_REPORT_") and os.path.exists(filename):
|
| 138 |
return send_file(filename, as_attachment=True)
|
| 139 |
-
return "Error
|
| 140 |
|
| 141 |
-
@app.route('/
|
| 142 |
-
def
|
| 143 |
session.clear()
|
| 144 |
return redirect(url_for('entry'))
|
| 145 |
|
| 146 |
-
# ---
|
| 147 |
-
|
| 148 |
T_LOGIN = """
|
| 149 |
-
<!DOCTYPE html><html><head>
|
|
|
|
|
|
|
| 150 |
<body class="login-screen">
|
| 151 |
<div class="jst-brand">{{team}}</div>
|
| 152 |
-
<div style="color:#
|
| 153 |
<div class="login-card">
|
| 154 |
-
<div style="font-size:0.8em; margin-bottom:20px;">REQUIRE IDENTIFICATION</div>
|
| 155 |
<form method="post">
|
| 156 |
-
<input type="password" name="p" placeholder="
|
| 157 |
<button type="submit">AUTHENTICATE</button>
|
| 158 |
</form>
|
| 159 |
</div>
|
|
@@ -161,64 +142,54 @@ T_LOGIN = """
|
|
| 161 |
"""
|
| 162 |
|
| 163 |
T_DASHBOARD = """
|
| 164 |
-
<!DOCTYPE html><html><head>
|
|
|
|
|
|
|
| 165 |
<div class="header">
|
| 166 |
-
<
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
</div>
|
| 170 |
-
|
| 171 |
<div class="container">
|
| 172 |
<div class="grid">
|
| 173 |
{% for t_id in allowed %}
|
| 174 |
<div class="card">
|
| 175 |
-
<h3>>>
|
| 176 |
<form onsubmit="runTool(event, '{{t_id}}')">
|
| 177 |
-
<input type="text" id="target-{{t_id}}" placeholder="Target
|
| 178 |
-
<button type="submit" id="btn-{{t_id}}">
|
| 179 |
</form>
|
| 180 |
-
|
| 181 |
<div id="sess-{{t_id}}" class="console-box">
|
| 182 |
-
<div class="console-header">
|
| 183 |
-
|
| 184 |
-
<a href="#" id="dl-{{t_id}}" class="dl-btn">SAVE_DUMP</a>
|
| 185 |
-
</div>
|
| 186 |
-
<div class="console-body">
|
| 187 |
-
<span id="out-{{t_id}}"></span><span id="cursor-{{t_id}}" class="blinking-cursor">_</span>
|
| 188 |
-
</div>
|
| 189 |
</div>
|
| 190 |
</div>
|
| 191 |
{% endfor %}
|
| 192 |
</div>
|
| 193 |
-
<br><center><a href="/exit" style="color:#333; text-decoration:none; font-size:0.8em; letter-spacing:2px;">[ CLOSE CONNECTION ]</a></center>
|
| 194 |
</div>
|
| 195 |
-
|
| 196 |
<script>
|
| 197 |
-
let currentEventSources = {};
|
| 198 |
-
|
| 199 |
function runTool(e, id) {
|
| 200 |
e.preventDefault();
|
| 201 |
const val = document.getElementById('target-'+id).value;
|
| 202 |
-
if(!val) return;
|
| 203 |
-
|
| 204 |
const out = document.getElementById('out-'+id);
|
| 205 |
const sess = document.getElementById('sess-'+id);
|
| 206 |
const dl = document.getElementById('dl-'+id);
|
| 207 |
const btn = document.getElementById('btn-'+id);
|
| 208 |
const cursor = document.getElementById('cursor-'+id);
|
| 209 |
|
| 210 |
-
if(currentEventSources[id]) { currentEventSources[id].close(); }
|
| 211 |
-
|
| 212 |
sess.style.display = 'flex';
|
| 213 |
out.innerHTML = "";
|
| 214 |
dl.style.display = 'none';
|
| 215 |
btn.disabled = true;
|
| 216 |
-
btn.innerText = "
|
| 217 |
cursor.style.display = "inline";
|
| 218 |
|
| 219 |
const source = new EventSource(`/stream/${id}?target=${val}`);
|
| 220 |
-
currentEventSources[id] = source;
|
| 221 |
-
|
| 222 |
source.onmessage = function(event) {
|
| 223 |
const data = JSON.parse(event.data);
|
| 224 |
if(data.line) {
|
|
@@ -228,19 +199,17 @@ function runTool(e, id) {
|
|
| 228 |
if(data.done) {
|
| 229 |
source.close();
|
| 230 |
btn.disabled = false;
|
| 231 |
-
btn.innerText = "
|
| 232 |
dl.href = "/dl/" + data.file;
|
| 233 |
dl.style.display = 'inline-block';
|
| 234 |
cursor.style.display = "none";
|
| 235 |
}
|
| 236 |
};
|
| 237 |
-
|
| 238 |
source.onerror = function() {
|
| 239 |
source.close();
|
| 240 |
btn.disabled = false;
|
| 241 |
-
btn.innerText = "
|
| 242 |
-
out.innerHTML += "\\n[!]
|
| 243 |
-
cursor.style.display = "none";
|
| 244 |
};
|
| 245 |
}
|
| 246 |
</script>
|
|
|
|
| 5 |
from flask import Flask, render_template_string, request, session, redirect, url_for, send_file, Response
|
| 6 |
|
| 7 |
app = Flask(__name__)
|
| 8 |
+
# Secret key statis biar login gak mental-mental
|
| 9 |
+
app.secret_key = "JST_STABLE_NODE_KEY_404_ZIE"
|
| 10 |
|
| 11 |
# --- IDENTITY & CREDENTIALS ---
|
| 12 |
OWNER_NAME = "WhyLaugh404"
|
|
|
|
| 25 |
"owner": ["sherlock", "maigret", "socialscan", "holehe", "phoneinfoga", "twint", "exiftool", "ghunt", "cloud_enum"]
|
| 26 |
}
|
| 27 |
|
|
|
|
| 28 |
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
| 29 |
|
| 30 |
+
# --- STYLING: RESPONSIVE J.S.T THEME ---
|
| 31 |
CSS = """
|
| 32 |
:root { --bg: #000000; --panel: #050505; --accent: #00ff00; --accent-glow: #00ff0044; --text: #00ff00; --border: #111; --danger: #ff003c; }
|
| 33 |
+
body { background: var(--bg); color: var(--text); font-family: 'JetBrains Mono', monospace; margin: 0; padding: 0; text-shadow: 0 0 5px var(--accent-glow); overflow-x: hidden; }
|
| 34 |
+
|
| 35 |
+
.login-screen { display: flex; align-items: center; justify-content: center; min-height: 100vh; flex-direction: column; padding: 20px; }
|
| 36 |
+
.jst-brand { font-size: 2em; font-weight: bold; letter-spacing: 5px; text-align: center; }
|
| 37 |
+
.login-card { background: var(--panel); border: 1px solid var(--accent); padding: 30px; width: 100%; max-width: 350px; box-sizing: border-box; box-shadow: 0 0 20px var(--accent-glow); }
|
| 38 |
+
|
| 39 |
+
.header { background: var(--panel); border-bottom: 1px solid var(--accent); padding: 15px; position: relative; display: flex; align-items: center; justify-content: space-between; }
|
| 40 |
+
.header-info { display: flex; align-items: center; gap: 15px; }
|
| 41 |
+
.mrx-logo { width: 50px; height: 50px; border-radius: 50%; border: 1px solid var(--accent); }
|
| 42 |
+
.logout-btn { color: var(--danger); text-decoration: none; border: 1px solid var(--danger); padding: 5px 12px; font-size: 0.7em; letter-spacing: 1px; font-weight: bold; transition: 0.3s; }
|
| 43 |
+
.logout-btn:hover { background: var(--danger); color: #fff; }
|
| 44 |
+
|
| 45 |
+
.container { width: 100%; max-width: 1400px; margin: 20px auto; padding: 0 15px; box-sizing: border-box; }
|
| 46 |
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
|
| 47 |
+
|
| 48 |
+
@media (max-width: 600px) {
|
| 49 |
+
.header { flex-direction: column; gap: 10px; text-align: center; }
|
| 50 |
+
.header-info { flex-direction: column; gap: 5px; }
|
| 51 |
+
.grid { grid-template-columns: 1fr; }
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.card { background: var(--panel); border: 1px solid #222; padding: 15px; }
|
| 55 |
+
.card:hover { border-color: var(--accent); }
|
| 56 |
+
input { background: transparent; border: 1px solid #333; color: var(--text); padding: 12px; width: 100%; box-sizing: border-box; margin-bottom: 10px; font-family: inherit; outline: none; }
|
| 57 |
+
button { background: transparent; border: 1px solid var(--accent); color: var(--accent); padding: 10px; width: 100%; cursor: pointer; font-weight: bold; }
|
|
|
|
| 58 |
button:hover:not(:disabled) { background: var(--accent); color: #000; }
|
|
|
|
| 59 |
|
| 60 |
.console-box { background: #000; border: 1px solid #222; margin-top: 15px; display: none; flex-direction: column; }
|
| 61 |
+
.console-header { background: #050505; padding: 8px; font-size: 0.7em; display: flex; justify-content: space-between; border-bottom: 1px solid #222; }
|
| 62 |
+
.console-body { padding: 10px; height: 300px; overflow-y: auto; font-size: 0.75em; white-space: pre-wrap; color: #cfc; }
|
| 63 |
+
.dl-btn { color: var(--accent); text-decoration: none; border: 1px solid var(--accent); padding: 2px 8px; font-size: 0.9em; display: none; }
|
|
|
|
| 64 |
.blinking-cursor { animation: blink 1s step-end infinite; }
|
| 65 |
@keyframes blink { 50% { opacity: 0; } }
|
| 66 |
"""
|
| 67 |
|
|
|
|
|
|
|
| 68 |
@app.route('/', methods=['GET', 'POST'])
|
| 69 |
def entry():
|
| 70 |
if request.method == 'POST':
|
|
|
|
| 73 |
if p == val:
|
| 74 |
session['role'] = role
|
| 75 |
return redirect(url_for('entry'))
|
| 76 |
+
return "<body style='background:#000; color:red; text-align:center; padding-top:10vh; font-family:monospace;'>[!] ACCESS DENIED</body>"
|
| 77 |
|
| 78 |
if 'role' in session:
|
| 79 |
return render_template_string(T_DASHBOARD, css=CSS, owner=OWNER_NAME, team=TEAM_NAME,
|
| 80 |
role=session['role'], allowed=ACCESS_LEVELS[session['role']], mrx_url=MRX_LOGO)
|
|
|
|
| 81 |
return render_template_string(T_LOGIN, css=CSS, team=TEAM_NAME, owner=OWNER_NAME)
|
| 82 |
|
| 83 |
@app.route('/stream/<tool_id>')
|
| 84 |
def stream(tool_id):
|
| 85 |
if 'role' not in session or tool_id not in ACCESS_LEVELS[session['role']]:
|
| 86 |
return Response("Unauthorized", status=403)
|
|
|
|
| 87 |
target = request.args.get('target', '').strip()
|
|
|
|
| 88 |
def generate():
|
| 89 |
cmds = {
|
| 90 |
"sherlock": f"sherlock {target} --timeout 5",
|
|
|
|
| 97 |
"ghunt": f"ghunt email {target}",
|
| 98 |
"cloud_enum": f"python3 /home/user/cloud_enum/cloud_enum.py -k {target}"
|
| 99 |
}
|
|
|
|
| 100 |
cmd = cmds.get(tool_id)
|
| 101 |
report_file = f"JST_REPORT_{tool_id.upper()}_{target.replace('.', '_')}.txt"
|
| 102 |
+
init_msg = f"> Initializing {tool_id.upper()} protocol...\n> Target: {target}\n\n"
|
|
|
|
|
|
|
| 103 |
yield f"data: {json.dumps({'line': init_msg})}\n\n"
|
|
|
|
| 104 |
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True)
|
|
|
|
| 105 |
full_log = ""
|
| 106 |
for line in process.stdout:
|
|
|
|
| 107 |
clean_line = ansi_escape.sub('', line)
|
| 108 |
full_log += clean_line
|
| 109 |
yield f"data: {json.dumps({'line': clean_line})}\n\n"
|
|
|
|
| 110 |
process.wait()
|
|
|
|
|
|
|
| 111 |
with open(report_file, "w") as f:
|
| 112 |
+
f.write(f"--- {TEAM_NAME} REPORT ---\nOP: {OWNER_NAME}\nTOOL: {tool_id.upper()}\n\n{full_log}")
|
|
|
|
| 113 |
yield f"data: {json.dumps({'done': True, 'file': report_file})}\n\n"
|
|
|
|
| 114 |
return Response(generate(), mimetype='text/event-stream')
|
| 115 |
|
| 116 |
@app.route('/dl/<filename>')
|
| 117 |
def dl(filename):
|
| 118 |
if filename.startswith("JST_REPORT_") and os.path.exists(filename):
|
| 119 |
return send_file(filename, as_attachment=True)
|
| 120 |
+
return "Error", 404
|
| 121 |
|
| 122 |
+
@app.route('/logout')
|
| 123 |
+
def logout():
|
| 124 |
session.clear()
|
| 125 |
return redirect(url_for('entry'))
|
| 126 |
|
| 127 |
+
# --- TEMPLATES ---
|
|
|
|
| 128 |
T_LOGIN = """
|
| 129 |
+
<!DOCTYPE html><html><head>
|
| 130 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 131 |
+
<title>{{team}} | AUTH</title><style>{{css}}</style></head>
|
| 132 |
<body class="login-screen">
|
| 133 |
<div class="jst-brand">{{team}}</div>
|
| 134 |
+
<div style="color:#444; letter-spacing:3px; margin-bottom:20px; font-size:0.8em;">[ CENTRAL NODE ]</div>
|
| 135 |
<div class="login-card">
|
|
|
|
| 136 |
<form method="post">
|
| 137 |
+
<input type="password" name="p" placeholder="PASSPHRASE" required autofocus>
|
| 138 |
<button type="submit">AUTHENTICATE</button>
|
| 139 |
</form>
|
| 140 |
</div>
|
|
|
|
| 142 |
"""
|
| 143 |
|
| 144 |
T_DASHBOARD = """
|
| 145 |
+
<!DOCTYPE html><html><head>
|
| 146 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 147 |
+
<title>{{team}} CONSOLE</title><style>{{css}}</style></head><body>
|
| 148 |
<div class="header">
|
| 149 |
+
<div class="header-info">
|
| 150 |
+
<img src="{{mrx_url}}" class="mrx-logo">
|
| 151 |
+
<div>
|
| 152 |
+
<div style="font-weight:bold; letter-spacing:2px; font-size:1.1em;">{{team}} CONSOLE</div>
|
| 153 |
+
<div style="font-size:0.6em; color:#555;">OPERATOR: {{owner}} | LVL: {{role.upper()}}</div>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
<a href="/logout" class="logout-btn">DISCONNECT</a>
|
| 157 |
</div>
|
|
|
|
| 158 |
<div class="container">
|
| 159 |
<div class="grid">
|
| 160 |
{% for t_id in allowed %}
|
| 161 |
<div class="card">
|
| 162 |
+
<h3>>> {{t_id.upper()}}</h3>
|
| 163 |
<form onsubmit="runTool(event, '{{t_id}}')">
|
| 164 |
+
<input type="text" id="target-{{t_id}}" placeholder="Target..." required>
|
| 165 |
+
<button type="submit" id="btn-{{t_id}}">START</button>
|
| 166 |
</form>
|
|
|
|
| 167 |
<div id="sess-{{t_id}}" class="console-box">
|
| 168 |
+
<div class="console-header"><span>LOG_{{t_id.upper()}}</span><a href="#" id="dl-{{t_id}}" class="dl-btn">SAVE</a></div>
|
| 169 |
+
<div class="console-body"><span id="out-{{t_id}}"></span><span id="cursor-{{t_id}}" class="blinking-cursor">_</span></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</div>
|
| 171 |
</div>
|
| 172 |
{% endfor %}
|
| 173 |
</div>
|
|
|
|
| 174 |
</div>
|
|
|
|
| 175 |
<script>
|
|
|
|
|
|
|
| 176 |
function runTool(e, id) {
|
| 177 |
e.preventDefault();
|
| 178 |
const val = document.getElementById('target-'+id).value;
|
|
|
|
|
|
|
| 179 |
const out = document.getElementById('out-'+id);
|
| 180 |
const sess = document.getElementById('sess-'+id);
|
| 181 |
const dl = document.getElementById('dl-'+id);
|
| 182 |
const btn = document.getElementById('btn-'+id);
|
| 183 |
const cursor = document.getElementById('cursor-'+id);
|
| 184 |
|
|
|
|
|
|
|
| 185 |
sess.style.display = 'flex';
|
| 186 |
out.innerHTML = "";
|
| 187 |
dl.style.display = 'none';
|
| 188 |
btn.disabled = true;
|
| 189 |
+
btn.innerText = "RUNNING...";
|
| 190 |
cursor.style.display = "inline";
|
| 191 |
|
| 192 |
const source = new EventSource(`/stream/${id}?target=${val}`);
|
|
|
|
|
|
|
| 193 |
source.onmessage = function(event) {
|
| 194 |
const data = JSON.parse(event.data);
|
| 195 |
if(data.line) {
|
|
|
|
| 199 |
if(data.done) {
|
| 200 |
source.close();
|
| 201 |
btn.disabled = false;
|
| 202 |
+
btn.innerText = "START";
|
| 203 |
dl.href = "/dl/" + data.file;
|
| 204 |
dl.style.display = 'inline-block';
|
| 205 |
cursor.style.display = "none";
|
| 206 |
}
|
| 207 |
};
|
|
|
|
| 208 |
source.onerror = function() {
|
| 209 |
source.close();
|
| 210 |
btn.disabled = false;
|
| 211 |
+
btn.innerText = "START";
|
| 212 |
+
out.innerHTML += "\\n[!] CONNECTION ERROR";
|
|
|
|
| 213 |
};
|
| 214 |
}
|
| 215 |
</script>
|