mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:17:39 +03:00
new stuff
This commit is contained in:
@@ -479,7 +479,30 @@ a:hover { text-decoration: underline; }
|
||||
}
|
||||
.th-drag:active { cursor: grabbing; }
|
||||
.th-label { flex: 1; min-width: 0; }
|
||||
.th-draggable.th-dragging { opacity: .55; }
|
||||
.th-draggable.th-dragging { opacity: .45; }
|
||||
.th-draggable.th-dragging .th-label {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.data-table th.th-drop-target {
|
||||
box-shadow: inset 0 -3px 0 var(--accent);
|
||||
background: var(--row-hover);
|
||||
}
|
||||
.col-drag-ghost {
|
||||
position: fixed;
|
||||
top: -1000px;
|
||||
left: -1000px;
|
||||
z-index: 9999;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-resizer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -548,20 +571,33 @@ a:hover { text-decoration: underline; }
|
||||
|
||||
.reports-search-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 10px 14px;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px 12px;
|
||||
width: 100%;
|
||||
margin: 12px 0 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.reports-search-label {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
align-self: center;
|
||||
}
|
||||
.reports-search-field {
|
||||
position: relative;
|
||||
flex: 1 1 280px;
|
||||
min-width: 200px;
|
||||
flex: 0 0 40%;
|
||||
width: 40%;
|
||||
max-width: 40%;
|
||||
align-self: center;
|
||||
}
|
||||
.reports-search-row .btn {
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
margin-left: 0;
|
||||
}
|
||||
.reports-search-input {
|
||||
width: 100%;
|
||||
|
||||
@@ -331,34 +331,79 @@
|
||||
cg.innerHTML = html;
|
||||
}
|
||||
|
||||
function columnLabelByKey(key) {
|
||||
const c = LIST_COLUMNS.find((col) => col.key === key);
|
||||
return c ? c.label : key;
|
||||
}
|
||||
|
||||
function clearColumnDropMarkers(thead) {
|
||||
thead.querySelectorAll('.th-drop-target').forEach((el) => {
|
||||
el.classList.remove('th-drop-target');
|
||||
});
|
||||
thead.querySelectorAll('.th-dragging').forEach((el) => {
|
||||
el.classList.remove('th-dragging');
|
||||
});
|
||||
}
|
||||
|
||||
function bindReportsTableLayout(table, thead, ctx) {
|
||||
if (!table || !thead || table.dataset.layoutBound === '1') return;
|
||||
table.dataset.layoutBound = '1';
|
||||
let dragKey = null;
|
||||
|
||||
thead.addEventListener('dragstart', (e) => {
|
||||
const handle = e.target.closest('.th-drag');
|
||||
if (!handle || ctx.getGrouped()) return;
|
||||
dragKey = handle.getAttribute('data-col-key');
|
||||
const sourceKey = handle.getAttribute('data-col-key');
|
||||
if (!sourceKey) return;
|
||||
table.dataset.dragColKey = sourceKey;
|
||||
const th = handle.closest('th');
|
||||
if (th) th.classList.add('th-dragging');
|
||||
const label = columnLabelByKey(sourceKey);
|
||||
const ghost = document.createElement('div');
|
||||
ghost.className = 'col-drag-ghost';
|
||||
ghost.textContent = label;
|
||||
ghost.setAttribute('aria-hidden', 'true');
|
||||
document.body.appendChild(ghost);
|
||||
table._dragGhost = ghost;
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', dragKey || '');
|
||||
e.dataTransfer.setData('text/plain', sourceKey);
|
||||
try {
|
||||
e.dataTransfer.setDragImage(ghost, 16, 14);
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
thead.addEventListener('dragend', (e) => {
|
||||
const th = e.target.closest('th');
|
||||
if (th) th.classList.remove('th-dragging');
|
||||
dragKey = null;
|
||||
thead.addEventListener('dragend', () => {
|
||||
clearColumnDropMarkers(thead);
|
||||
delete table.dataset.dragColKey;
|
||||
if (table._dragGhost) {
|
||||
table._dragGhost.remove();
|
||||
table._dragGhost = null;
|
||||
}
|
||||
});
|
||||
|
||||
thead.addEventListener('dragenter', (e) => {
|
||||
if (ctx.getGrouped() || !table.dataset.dragColKey) return;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
thead.addEventListener('dragover', (e) => {
|
||||
if (ctx.getGrouped()) return;
|
||||
if (!e.target.closest('th[data-col-key]')) return;
|
||||
if (ctx.getGrouped() || !table.dataset.dragColKey) return;
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
||||
const th = e.target.closest('th[data-col-key]');
|
||||
if (!th) return;
|
||||
const active = thead.querySelector('.th-drop-target');
|
||||
if (active && active !== th) active.classList.remove('th-drop-target');
|
||||
th.classList.add('th-drop-target');
|
||||
});
|
||||
|
||||
thead.addEventListener('dragleave', (e) => {
|
||||
const th = e.target.closest('th[data-col-key]');
|
||||
if (!th) return;
|
||||
const rel = e.relatedTarget;
|
||||
if (rel && th.contains(rel)) return;
|
||||
th.classList.remove('th-drop-target');
|
||||
});
|
||||
|
||||
thead.addEventListener('drop', (e) => {
|
||||
@@ -366,9 +411,18 @@
|
||||
const th = e.target.closest('th[data-col-key]');
|
||||
if (!th) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const sourceKey =
|
||||
(e.dataTransfer && e.dataTransfer.getData('text/plain')) || dragKey;
|
||||
table.dataset.dragColKey ||
|
||||
(e.dataTransfer && e.dataTransfer.getData('text/plain')) ||
|
||||
'';
|
||||
const targetKey = th.getAttribute('data-col-key');
|
||||
clearColumnDropMarkers(thead);
|
||||
delete table.dataset.dragColKey;
|
||||
if (table._dragGhost) {
|
||||
table._dragGhost.remove();
|
||||
table._dragGhost = null;
|
||||
}
|
||||
if (!sourceKey || !targetKey || sourceKey === targetKey) return;
|
||||
const order = getOrderedColumnKeys();
|
||||
const from = order.indexOf(sourceKey);
|
||||
@@ -377,7 +431,6 @@
|
||||
order.splice(from, 1);
|
||||
order.splice(to, 0, sourceKey);
|
||||
saveColumnOrder(order);
|
||||
dragKey = null;
|
||||
ctx.refreshLayout();
|
||||
});
|
||||
|
||||
@@ -907,7 +960,6 @@
|
||||
const searchSuggest = document.getElementById('reports-search-suggest');
|
||||
const searchClear = document.getElementById('reports-search-clear');
|
||||
let suggestTimer = null;
|
||||
let searchRunTimer = null;
|
||||
|
||||
function hideSuggest() {
|
||||
if (searchSuggest) {
|
||||
@@ -988,22 +1040,18 @@
|
||||
if (searchClear) searchClear.hidden = false;
|
||||
}
|
||||
searchInput.addEventListener('input', () => {
|
||||
const q = searchInput.value.trim();
|
||||
if (searchClear) searchClear.hidden = !q;
|
||||
const q = searchInput.value;
|
||||
if (searchClear) searchClear.hidden = !q.trim();
|
||||
hideSuggest();
|
||||
clearTimeout(suggestTimer);
|
||||
clearTimeout(searchRunTimer);
|
||||
if (q.length < 2) {
|
||||
hideSuggest();
|
||||
return;
|
||||
}
|
||||
suggestTimer = setTimeout(() => fetchSuggest(q), 1000);
|
||||
searchRunTimer = setTimeout(() => applySearchQuery(q, true), 1600);
|
||||
const trimmed = q.trim();
|
||||
if (trimmed.length < 2) return;
|
||||
suggestTimer = setTimeout(() => fetchSuggest(trimmed), 1000);
|
||||
});
|
||||
searchInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
clearTimeout(suggestTimer);
|
||||
clearTimeout(searchRunTimer);
|
||||
hideSuggest();
|
||||
applySearchQuery(searchInput.value.trim(), true);
|
||||
}
|
||||
@@ -1012,14 +1060,26 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
function appendSearchTerm(term) {
|
||||
if (!searchInput) return;
|
||||
const t = (term || '').trim();
|
||||
if (!t) return;
|
||||
const cur = searchInput.value.trim();
|
||||
if (!cur) {
|
||||
searchInput.value = t;
|
||||
} else if (!cur.toLowerCase().includes(t.toLowerCase())) {
|
||||
searchInput.value = cur + ' ' + t;
|
||||
}
|
||||
if (searchClear) searchClear.hidden = !searchInput.value.trim();
|
||||
searchInput.focus();
|
||||
}
|
||||
if (searchSuggest) {
|
||||
searchSuggest.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-suggest]');
|
||||
if (!btn) return;
|
||||
const term = btn.getAttribute('data-suggest') || '';
|
||||
if (searchInput) searchInput.value = term;
|
||||
appendSearchTerm(term);
|
||||
hideSuggest();
|
||||
applySearchQuery(term, true);
|
||||
});
|
||||
}
|
||||
if (searchClear) {
|
||||
|
||||
101
examples/crash_reporter/upload_test.sh
Executable file
101
examples/crash_reporter/upload_test.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# Batch-upload fake crash reports (java + native) from test_reports/.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPORTS_DIR="${REPORTS_DIR:-$ROOT/test_reports}"
|
||||
BASE="${BASE:-https://apps.f0xx.org/app/androidcast_project/crashes}"
|
||||
UPLOAD_URL="$BASE/api/upload.php"
|
||||
JAR="${JAR:-/tmp/ac_crash_cookies.txt}"
|
||||
LOGIN="${LOGIN:-0}"
|
||||
DELAY_MS="${DELAY_MS:-50}"
|
||||
ONLY="${ONLY:-all}" # all | java | native
|
||||
|
||||
if [[ ! -d "$REPORTS_DIR" ]]; then
|
||||
echo "Missing $REPORTS_DIR — run: python3 $ROOT/generate_test_reports.py" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
FILES=()
|
||||
case "$ONLY" in
|
||||
java)
|
||||
FILES=("$REPORTS_DIR"/crash_java_*.json)
|
||||
;;
|
||||
native)
|
||||
FILES=("$REPORTS_DIR"/crash_native_*.json)
|
||||
;;
|
||||
all|*)
|
||||
FILES=("$REPORTS_DIR"/crash_java_*.json "$REPORTS_DIR"/crash_native_*.json)
|
||||
# legacy single-prefix files from older generator
|
||||
LEGACY=("$REPORTS_DIR"/crash_[0-9][0-9][0-9].json)
|
||||
if [[ ${#FILES[@]} -eq 0 && ${#LEGACY[@]} -gt 0 ]]; then
|
||||
FILES=("${LEGACY[@]}")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ ${#FILES[@]} -eq 0 ]]; then
|
||||
echo "No reports in $REPORTS_DIR (expected crash_java_*.json / crash_native_*.json)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS=$'\n' FILES=($(printf '%s\n' "${FILES[@]}" | sort))
|
||||
unset IFS
|
||||
|
||||
java_n=0
|
||||
native_n=0
|
||||
for f in "${FILES[@]}"; do
|
||||
case "$(basename "$f")" in
|
||||
crash_java_*) ((java_n++)) || true ;;
|
||||
crash_native_*) ((native_n++)) || true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$LOGIN" == "1" ]]; then
|
||||
curl -sk -c "$JAR" -b "$JAR" -L -X POST "$BASE/login" \
|
||||
-d 'username=admin&password=admin' -o /dev/null
|
||||
echo "Logged in (cookie jar: $JAR)"
|
||||
fi
|
||||
|
||||
ok=0
|
||||
fail=0
|
||||
dup=0
|
||||
ok_java=0
|
||||
ok_native=0
|
||||
|
||||
echo "Uploading ${#FILES[@]} reports to $UPLOAD_URL (java=$java_n native=$native_n only=$ONLY)"
|
||||
|
||||
for f in "${FILES[@]}"; do
|
||||
name="$(basename "$f")"
|
||||
body="$(curl -sk -w '\n%{http_code}' -H 'Content-Type: application/json' --data-binary "@$f" "$UPLOAD_URL")"
|
||||
code="$(echo "$body" | tail -n1)"
|
||||
json="$(echo "$body" | sed '$d')"
|
||||
|
||||
case "$code" in
|
||||
200)
|
||||
((ok++)) || true
|
||||
case "$name" in
|
||||
crash_java_*) ((ok_java++)) || true; echo " OK [java] $name" ;;
|
||||
crash_native_*) ((ok_native++)) || true; echo " OK [native] $name" ;;
|
||||
*) echo " OK $name" ;;
|
||||
esac
|
||||
;;
|
||||
409)
|
||||
((dup++)) || true
|
||||
echo " DUP $name (report_id already stored)"
|
||||
;;
|
||||
*)
|
||||
((fail++)) || true
|
||||
echo " FAIL $name HTTP $code — $json" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$DELAY_MS" -gt 0 ]]; then
|
||||
sleep "$(awk "BEGIN { print $DELAY_MS / 1000 }")"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Done: ok=$ok (java=$ok_java native=$ok_native) duplicate=$dup failed=$fail (total=${#FILES[@]})"
|
||||
[[ "$fail" -eq 0 ]]
|
||||
Reference in New Issue
Block a user