abc81e60c8
Single Go binary with embedded Bootstrap 3 + jQuery UI, querying logs.raw over the ClickHouse HTTP interface as the readonly logreader user. Runs behind oauth2-proxy; the app does no auth itself. Server-side enforced time bounds (15m default, 72h max), parameterized queries, raw-SQL WHERE fragment wrapped with enforced bounds and LIMIT, tail polling with a clamped cursor, facets, healthz. Woodpecker build/test plus tag-driven image push to artifactapi docker-internal.
123 lines
5.1 KiB
HTML
123 lines
5.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>logviewer</title>
|
|
<link rel="stylesheet" href="/static/bootstrap.min.css">
|
|
<link rel="stylesheet" href="/static/logviewer.css">
|
|
</head>
|
|
<body>
|
|
<nav class="navbar navbar-inverse navbar-static-top">
|
|
<div class="container-fluid">
|
|
<div class="navbar-header">
|
|
<a class="navbar-brand" href="/">logviewer</a>
|
|
</div>
|
|
<p class="navbar-text navbar-right" id="status-text"></p>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container-fluid">
|
|
<form id="search-form" onsubmit="return false;">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="btn-group" role="group" id="range-buttons">
|
|
<button type="button" class="btn btn-default range-btn active" data-range="15m">15m</button>
|
|
<button type="button" class="btn btn-default range-btn" data-range="1h">1h</button>
|
|
<button type="button" class="btn btn-default range-btn" data-range="6h">6h</button>
|
|
<button type="button" class="btn btn-default range-btn" data-range="1d">1d</button>
|
|
<button type="button" class="btn btn-default range-btn" data-range="custom">custom</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="q" placeholder="fuzzy search (space-separated terms, all must match)">
|
|
<span class="input-group-btn">
|
|
<button class="btn btn-primary" type="button" id="search-btn">Search</button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 text-right">
|
|
<button type="button" class="btn btn-default" id="sql-toggle">SQL</button>
|
|
<button type="button" class="btn btn-default" id="tail-toggle">Tail</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" id="custom-range" style="display:none; margin-top:8px;">
|
|
<div class="col-md-3">
|
|
<div class="input-group">
|
|
<span class="input-group-addon">since</span>
|
|
<input type="datetime-local" class="form-control" id="since-input" step="1">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="input-group">
|
|
<span class="input-group-addon">until</span>
|
|
<input type="datetime-local" class="form-control" id="until-input" step="1">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" id="sql-row" style="display:none; margin-top:8px;">
|
|
<div class="col-md-12">
|
|
<textarea class="form-control mono" id="sql" rows="2"
|
|
placeholder="raw WHERE fragment, e.g. severity = 'error' AND message LIKE '%timeout%' (runs as readonly user, time bounds and LIMIT always enforced)"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" style="margin-top:8px;">
|
|
<div class="col-md-2"><select class="form-control" id="f-namespace"><option value="">namespace</option></select></div>
|
|
<div class="col-md-2"><select class="form-control" id="f-app"><option value="">app</option></select></div>
|
|
<div class="col-md-2"><select class="form-control" id="f-host"><option value="">host</option></select></div>
|
|
<div class="col-md-2"><input type="text" class="form-control" id="f-pod" placeholder="pod"></div>
|
|
<div class="col-md-1"><input type="text" class="form-control" id="f-container" placeholder="container"></div>
|
|
<div class="col-md-2">
|
|
<select class="form-control" id="f-severity">
|
|
<option value="">severity</option>
|
|
<option>fatal</option><option>error</option><option>warn</option>
|
|
<option>info</option><option>debug</option><option>trace</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<select class="form-control" id="f-stream">
|
|
<option value="">stream</option>
|
|
<option>stdout</option><option>stderr</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="row" style="margin-top:10px;">
|
|
<div class="col-md-6">
|
|
<p id="result-info" class="text-muted"></p>
|
|
</div>
|
|
<div class="col-md-6 text-right" id="pager">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-default btn-sm" id="prev-btn" disabled>« newer</button>
|
|
<button type="button" class="btn btn-default btn-sm" id="next-btn" disabled>older »</button>
|
|
</div>
|
|
<select id="page-size" class="btn btn-default btn-sm">
|
|
<option>100</option><option>200</option><option>500</option><option>1000</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table table-condensed table-hover" id="log-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="col-ts">timestamp (UTC)</th>
|
|
<th class="col-src">namespace / pod</th>
|
|
<th class="col-sev">severity</th>
|
|
<th>message</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="log-body"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script src="/static/jquery.min.js"></script>
|
|
<script src="/static/bootstrap.min.js"></script>
|
|
<script src="/static/app.js"></script>
|
|
</body>
|
|
</html>
|