mirror of https://github.com/grafana/loki
Logql analyzer page cherry picks (#6889)
* added Logql Analyzer page into loki docs * changed template to support changes in API and supported the case when query is run against empty line * added more json examples, changed some labels and changes css for line numbers * fixed last comments from Karen and pointed to production APIpull/6890/head
parent
3c5fdb749a
commit
bec014dd86
@ -0,0 +1,219 @@ |
||||
--- |
||||
title: LoqQL Analyzer |
||||
weight: 60 |
||||
--- |
||||
|
||||
<link rel="stylesheet" href="../analyzer/style.css"> |
||||
<script src="../analyzer/handlebars.js"></script> |
||||
|
||||
# LogQL Analyzer |
||||
|
||||
<main class="logql-analyzer"> |
||||
<section class="logs-source panel-container"> |
||||
<div class="logs-source__header"> |
||||
<div class="examples"> |
||||
<span>Log line format:</span> |
||||
<span class="example"> |
||||
<input type="radio" class="example-select" name="example" id="logfmt-example" checked> |
||||
<label for="logfmt-example">logfmt</label> |
||||
</span> |
||||
<span class="example"> |
||||
<input type="radio" class="example-select" name="example" id="json-parser-example"> |
||||
<label for="json-parser-example">JSON</label> |
||||
</span> |
||||
<span class="example"> |
||||
<input type="radio" class="example-select" name="example" id="pattern-parser-example"> |
||||
<label for="pattern-parser-example">Unstructured text</label> |
||||
</span> |
||||
</div> |
||||
<div class="share-section"> |
||||
<span class="share-link-copied-notification hide" id="share-link-copied-notification"> |
||||
<i class="fa fa-check" aria-hidden="true"></i> |
||||
Link copied to clipboard. |
||||
</span> |
||||
<button class="primary-button" id="share-button"> |
||||
<i class="fa fa-link" aria-hidden="true"></i> |
||||
Share |
||||
</button> |
||||
</div> |
||||
</div> |
||||
<div class="panel-header"> |
||||
{job="analyze"} |
||||
</div> |
||||
<textarea id="logs-source-input" class="logs-source__input"></textarea> |
||||
</section> |
||||
<section class="query panel-container"> |
||||
<div class="panel-header"> |
||||
Query: |
||||
</div> |
||||
<div class="query-container"> |
||||
<div class="input-box"> |
||||
<span class="prefix">{job="analyze"} </span> |
||||
<input id="query-input" class="query_input"> |
||||
</div> |
||||
<button class="query_submit primary-button">Run query</button> |
||||
</div> |
||||
<div class="query-error" id="query-error"></div> |
||||
</section> |
||||
<section class="results panel-container hide" id="results"> |
||||
</section> |
||||
|
||||
</main> |
||||
|
||||
<script id="log-result-template" type="text/x-handlebars-template"> |
||||
<div class="panel-header"> |
||||
Results |
||||
</div> |
||||
{{#each results}} |
||||
<article class="debug-result-row"> |
||||
<div class="last-stage-result" data-line-index="{{@index}}"> |
||||
<div class="line-index"> |
||||
<div class="line-index__wrapper"> |
||||
<i class="line-cursor expand-cursor"></i> |
||||
<span>Line {{inc @index}}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
{{#if this.log_result}} |
||||
<span {{#if this.filtered_out}}class="filtered-out"{{/if}}> |
||||
{{this.log_result}} |
||||
</span> |
||||
{{/if}} |
||||
{{#unless this.log_result}} |
||||
<span class="note-text">(empty line)</span> |
||||
{{/unless}} |
||||
</div> |
||||
|
||||
<div class="debug-result-row__explain hide"> |
||||
<div class="explain-section origin-line"> |
||||
<div class="explain-section__header"> |
||||
Original log line |
||||
<span class="stage-expression">{{../stream_selector}}</span> |
||||
</div> |
||||
<div class="explain-section__body"> |
||||
{{this.origin_line}} |
||||
{{#unless this.log_result}} |
||||
<span class="note-text">(empty line)</span> |
||||
{{/unless}} |
||||
</div> |
||||
</div> |
||||
{{#each this.stages}} |
||||
<div class="arrow-wrapper"> |
||||
<i class="fa fa-arrow-down" aria-hidden="true"></i> |
||||
</div> |
||||
<div class="explain-section stage-line"> |
||||
<div class="explain-section__header"> |
||||
<span>stage #{{inc @index}}:</span> |
||||
<span class="stage-expression"> {{stage_expression}} </span> |
||||
</div> |
||||
<div class="explain-section__body"> |
||||
<div class="explain-section__row"> |
||||
<div class="explain-section__row-title"> |
||||
Available labels on this stage: |
||||
</div> |
||||
<div class="explain-section__row-body"> |
||||
{{#unless labels_before}} |
||||
<span>none</span> |
||||
{{/unless}} |
||||
{{#if labels_before}} |
||||
{{#each labels_before}} |
||||
<article class="label-value" style="background-color: {{background_color}}"> |
||||
{{name}}={{value}} |
||||
</article> |
||||
{{/each}} |
||||
{{/if}} |
||||
</div> |
||||
</div> |
||||
<div class="explain-section__row"> |
||||
<div class="explain-section__row-title"> |
||||
Line after this stage: |
||||
</div> |
||||
<div class="explain-section__row-body"> |
||||
{{#if line_after}} |
||||
<span {{#if this.filtered_out}}class="filtered-out"{{/if}}> |
||||
{{line_after}} |
||||
</span> |
||||
{{/if}} |
||||
{{#unless line_after}} |
||||
<span class="note-text">(empty line)</span> |
||||
{{/unless}} |
||||
{{#if this.filtered_out}} |
||||
<span class="important-text">the line has been filtered out on this stage</span> |
||||
{{/if}} |
||||
</div> |
||||
</div> |
||||
{{#if added_labels}} |
||||
<div class="explain-section__row"> |
||||
<div class="explain-section__row-title"> |
||||
Added/Modified labels: |
||||
</div> |
||||
<div class="explain-section__row-body"> |
||||
{{#each added_labels}} |
||||
<article class="label-value" style="background-color: {{background_color}}"> |
||||
{{name}}={{value}} |
||||
</article> |
||||
{{/each}} |
||||
</div> |
||||
</div> |
||||
{{/if}} |
||||
</div> |
||||
</div> |
||||
{{/each}} |
||||
</div> |
||||
</article> |
||||
{{/each}} |
||||
</script> |
||||
|
||||
[//]: # (Logfmt examples) |
||||
<script type="text/plain" id="logfmt-example-logs"> |
||||
level=info ts=2022-03-23T11:55:29.846163306Z caller=main.go:112 msg="Starting Grafana Enterprise Logs" |
||||
level=debug ts=2022-03-23T11:55:29.846226372Z caller=main.go:113 version=v1.3.0 branch=HEAD Revision=e071a811 LokiVersion=v2.4.2 LokiRevision=525040a3 |
||||
level=warn ts=2022-03-23T11:55:45.213901602Z caller=added_modules.go:198 msg="found valid license" cluster=enterprise-logs-test-fixture |
||||
level=info ts=2022-03-23T11:55:45.214611239Z caller=server.go:269 http=[::]:3100 grpc=[::]:9095 msg="server listening on addresses" |
||||
level=debug ts=2022-03-23T11:55:45.219665469Z caller=module_service.go:64 msg=initialising module=license |
||||
level=warm ts=2022-03-23T11:55:45.219678992Z caller=module_service.go:64 msg=initialising module=server |
||||
level=error ts=2022-03-23T11:55:45.221140583Z caller=manager.go:132 msg="license manager up and running" |
||||
level=info ts=2022-03-23T11:55:45.221254326Z caller=loki.go:355 msg="Loki started" |
||||
</script> |
||||
|
||||
<script type="text/plain" id="logfmt-example-query"> |
||||
| logfmt | level = "info" |
||||
</script> |
||||
|
||||
[//]: # (Json parser examples) |
||||
<script type="text/plain" id="json-parser-example-logs"> |
||||
{"timestamp":"2022-04-26T08:53:59.61Z","level":"INFO","class":"org.springframework.boot.SpringApplication","method":"logStartupProfileInfo","file":"SpringApplication.java","line":663,"thread":"restartedMain","message":"The following profiles are active: no-schedulers,json-logging"} |
||||
{"timestamp":"2022-04-26T08:53:59.645Z","level":"DEBUG","class":"org.springframework.boot.logging.DeferredLog","method":"logTo","file":"DeferredLog.java","line":255,"thread":"restartedMain","message":"Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable"} |
||||
{"timestamp":"2022-04-26T08:53:59.645Z","level":"DEBUG","class":"org.springframework.boot.logging.DeferredLog","method":"logTo","file":"DeferredLog.java","line":255,"thread":"restartedMain","message":"For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'"} |
||||
{"timestamp":"2022-04-26T08:54:00.274Z","level":"INFO","class":"org.springframework.data.repository.config.RepositoryConfigurationDelegate","method":"registerRepositoriesIn","file":"RepositoryConfigurationDelegate.java","line":132,"thread":"restartedMain","message":"Bootstrapping Spring Data JPA repositories in DEFAULT mode."} |
||||
{"timestamp":"2022-04-26T08:54:00.327Z","level":"INFO","class":"org.springframework.data.repository.config.RepositoryConfigurationDelegate","method":"registerRepositoriesIn","file":"RepositoryConfigurationDelegate.java","line":201,"thread":"restartedMain","message":"Finished Spring Data repository scanning in 47 ms. Found 3 JPA repository interfaces."} |
||||
{"timestamp":"2022-04-26T08:54:00.704Z","level":"INFO","class":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer","method":"initialize","file":"TomcatWebServer.java","line":108,"thread":"restartedMain","message":"Tomcat initialized with port(s): 8080 (http)"} |
||||
{"timestamp":"2022-06-16T10:54:47.466Z","level":"INFO","class":"org.apache.juli.logging.DirectJDKLog","method":"log","file":"DirectJDKLog.java","line":173,"thread":"restartedMain","message":"Starting service [Tomcat]"} |
||||
{"timestamp":"2022-06-16T10:54:47.467Z","level":"INFO","class":"org.apache.juli.logging.DirectJDKLog","method":"log","file":"DirectJDKLog.java","line":173,"thread":"restartedMain","message":"Starting Servlet engine: [Apache Tomcat/9.0.52]"} |
||||
</script> |
||||
|
||||
<script type="text/plain" id="json-parser-example-query"> |
||||
| json | level="INFO" | line_format "{{.message}}" |
||||
</script> |
||||
|
||||
|
||||
[//]: # (Pattern parser examples) |
||||
<script type="text/plain" id="pattern-parser-example-logs"> |
||||
238.46.18.83 - - [09/Jun/2022:14:13:44 -0700] "PUT /target/next-generation HTTP/2.0" 404 19042 |
||||
16.97.233.22 - - [09/Jun/2022:14:13:44 -0700] "DELETE /extensible/functionalities HTTP/1.0" 200 27913 |
||||
46.201.144.32 - - [09/Jun/2022:14:13:44 -0700] "PUT /e-enable/enable HTTP/2.0" 504 26885 |
||||
33.122.3.191 - corkery3759 [09/Jun/2022:14:13:44 -0700] "POST /extensible/dynamic/enable HTTP/2.0" 100 23741 |
||||
94.115.144.32 - damore5842 [09/Jun/2022:14:13:44 -0700] "PUT /matrix/envisioneer HTTP/1.0" 205 29993 |
||||
145.250.221.107 - price8727 [09/Jun/2022:14:13:44 -0700] "PUT /iterate/networks/e-business/action-items HTTP/1.0" 302 9718 |
||||
33.201.165.66 - - [09/Jun/2022:14:13:44 -0700] "GET /web-enabled/bricks-and-clicks HTTP/1.0" 205 2353 |
||||
33.83.191.176 - kling8903 [09/Jun/2022:14:13:44 -0700] "DELETE /architect HTTP/1.1" 401 13783 |
||||
</script> |
||||
|
||||
<script type="text/plain" id="pattern-parser-example-query"> |
||||
| pattern "<_> - <_> <_> \"<method> <url> <protocol>\" <status> <_> <_> \"<_>\" <_>" | status >= 200 and status < 300 |
||||
</script> |
||||
|
||||
|
||||
<script src="../analyzer/script.js"> </script> |
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,183 @@ |
||||
let host = "https://logql-analyzer.grafana.com/2-6-x"; |
||||
Handlebars.registerHelper("inc", (val) => parseInt(val) + 1); |
||||
let streamSelector = `{job="analyze"}`; |
||||
const logsSourceInputElement = document.getElementById("logs-source-input"); |
||||
const queryInputElement = document.getElementById("query-input"); |
||||
const resultsElement = document.getElementById("results"); |
||||
|
||||
function initListeners() { |
||||
[...document.getElementsByClassName("query_submit")].forEach(btn => btn.addEventListener("click", runQuery)); |
||||
[...document.getElementsByClassName("example-select")].forEach(btn => { |
||||
btn.addEventListener("click", e => { |
||||
if (!btn.checked) { |
||||
return |
||||
} |
||||
loadExample(e.currentTarget.id); |
||||
runQuery(); |
||||
}); |
||||
}); |
||||
document.getElementById("share-button").addEventListener("click", copySharableLink) |
||||
} |
||||
|
||||
let linkCopiedNotificationElement = document.getElementById("share-link-copied-notification"); |
||||
|
||||
function copySharableLink() { |
||||
linkCopiedNotificationElement.classList.add("hide") |
||||
let extractedData = getDataFromInputs(); |
||||
let urlParam = new URLSearchParams(); |
||||
urlParam.set("query", extractedData.query); |
||||
extractedData.logs.forEach(line => urlParam.append("line[]", line)); |
||||
let currentUrl = window.location.href; |
||||
let sharableLink = window.location.origin + window.location.pathname + "?" + urlParam.toString(); |
||||
window.history.pushState(null, null, sharableLink); |
||||
navigator.clipboard.writeText(sharableLink) |
||||
.then(() => { |
||||
linkCopiedNotificationElement.classList.remove("hide"); |
||||
setTimeout(() => linkCopiedNotificationElement.classList.add("hide"), 2000) |
||||
}) |
||||
} |
||||
|
||||
function loadCheckedExample() { |
||||
let selectedQueryExample = document.querySelector(".example-select:checked").id; |
||||
loadExample(selectedQueryExample) |
||||
} |
||||
|
||||
function updateInputs(logLines, query) { |
||||
logsSourceInputElement.value = logLines.trim(); |
||||
queryInputElement.value = query.trim(); |
||||
} |
||||
|
||||
function loadExample(exampleId) { |
||||
let logLinesElement = document.getElementById(exampleId + "-logs"); |
||||
let queryExampleElement = document.getElementById(exampleId + "-query"); |
||||
updateInputs(logLinesElement.innerText, queryExampleElement.innerText); |
||||
} |
||||
|
||||
function toggleExplainSection(event) { |
||||
let lineSection = document.getElementsByClassName("debug-result-row").item(event.currentTarget.dataset.lineIndex); |
||||
[...lineSection.getElementsByClassName("debug-result-row__explain")].forEach(explainSection => explainSection.classList.toggle("hide")); |
||||
[...lineSection.getElementsByClassName("line-cursor")].forEach(lineCursor => { |
||||
lineCursor.classList.toggle("expand-cursor"); |
||||
lineCursor.classList.toggle("collapse-cursor"); |
||||
}); |
||||
} |
||||
|
||||
function getDataFromInputs() { |
||||
let logs = logsSourceInputElement.value.split("\n"); |
||||
let query = queryInputElement.value; |
||||
return {logs, query} |
||||
} |
||||
|
||||
function runQuery() { |
||||
resultsElement.classList.add("hide"); |
||||
const data = getDataFromInputs(); |
||||
sendRequest({...data, query: `${streamSelector} ${data.query}`}) |
||||
.then((response) => handleResponse(response)) |
||||
} |
||||
|
||||
async function handleResponse(response) { |
||||
if (response.status !== 200) { |
||||
handleError(await response.text()); |
||||
return |
||||
} |
||||
renderResponse(await response.json()) |
||||
} |
||||
|
||||
function handleError(error) { |
||||
document.getElementById("query-error").innerHTML = error |
||||
document.getElementById("query-error").classList.remove("hide"); |
||||
resultsElement.classList.add("hide"); |
||||
} |
||||
|
||||
async function sendRequest(payload) { |
||||
return fetch(host + "/api/logql-analyze", { |
||||
method: 'POST', headers: { |
||||
'Accept': 'application/json', 'Content-Type': 'application/json' |
||||
}, mode: 'cors', body: JSON.stringify(payload) |
||||
}); |
||||
} |
||||
|
||||
function findAddedLabels(stageInfo) { |
||||
const labelsBefore = new Map(stageInfo.labels_before.map(it => [it.name, it.value])); |
||||
return stageInfo.labels_after |
||||
.filter(labelValue => labelsBefore.get(labelValue.name) !== labelValue.value); |
||||
} |
||||
|
||||
function adjustStagesModel(stages, response) { |
||||
return stages.map((stageInfo, stageIndex) => { |
||||
const addedLabels = findAddedLabels(stageInfo); |
||||
return { |
||||
...stageInfo, |
||||
labels_before: computeLabelColor(stageInfo.labels_before), |
||||
labels_after: computeLabelColor(stageInfo.labels_after), |
||||
added_labels: computeLabelColor(addedLabels), |
||||
stage_expression: response.stages[stageIndex] |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function adjustResponseModel(response) { |
||||
return { |
||||
...response, |
||||
results: response.results.map(result => { |
||||
let stages = result.stage_records; |
||||
return { |
||||
...result, |
||||
log_result: stages.length > 0 ? stages[stages.length - 1].line_after : result.origin_line, |
||||
filtered_out: stages.some(st => st.filtered_out), |
||||
stages: adjustStagesModel(stages, response) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function computeLabelColor(labels) { |
||||
return labels.map(labelValue => { |
||||
return {...labelValue, background_color: getBackgroundColor(labelValue.name)} |
||||
}) |
||||
} |
||||
|
||||
function initResultsSectionListeners() { |
||||
[...document.getElementsByClassName("last-stage-result")].forEach(row => row.addEventListener("click", toggleExplainSection)); |
||||
} |
||||
|
||||
function renderResponse(response) { |
||||
resetErrorContainer(); |
||||
const adjustedResponse = adjustResponseModel(response); |
||||
const rawResultsTemplate = document.getElementById("log-result-template").innerHTML; |
||||
const template = Handlebars.compile(rawResultsTemplate); |
||||
resultsElement.innerHTML = template(adjustedResponse); |
||||
resultsElement.classList.remove("hide"); |
||||
initResultsSectionListeners(); |
||||
} |
||||
|
||||
function resetErrorContainer() { |
||||
let errorContainer = document.getElementById("query-error"); |
||||
errorContainer.classList.add("hide") |
||||
errorContainer.innerText = ""; |
||||
} |
||||
|
||||
function getBackgroundColor(stringInput) { |
||||
let stringUniqueHash = [...stringInput].reduce((acc, char) => { |
||||
return char.charCodeAt(0) + ((acc << 5) - acc); |
||||
}, 0); |
||||
return `hsl(${stringUniqueHash % 360}, 95%, 35%, 15%)`; |
||||
} |
||||
|
||||
function loadExampleFromUrlIfExist() { |
||||
const urlSearchParams = new URLSearchParams(window.location.search); |
||||
const query = urlSearchParams.get("query"); |
||||
if (!query) { |
||||
return |
||||
} |
||||
const logLines = urlSearchParams.getAll("line[]") |
||||
.join("\n"); |
||||
updateInputs(logLines, query); |
||||
runQuery() |
||||
} |
||||
|
||||
initListeners(); |
||||
|
||||
loadCheckedExample(); |
||||
|
||||
loadExampleFromUrlIfExist(); |
@ -0,0 +1,222 @@ |
||||
.hide { |
||||
display: none !important; |
||||
} |
||||
|
||||
.logql-analyzer { |
||||
word-break: break-all; |
||||
position: relative; |
||||
display: grid; |
||||
grid-template-rows: 4fr 1fr auto; |
||||
} |
||||
|
||||
.logs-source { |
||||
display: grid; |
||||
grid-template-rows: 1fr 1fr 9fr; |
||||
} |
||||
|
||||
.examples { |
||||
display: flex; |
||||
column-gap: 10px; |
||||
align-items: center; |
||||
align-content: center; |
||||
} |
||||
|
||||
.example { |
||||
display: flex; |
||||
align-items: center; |
||||
column-gap: 5px; |
||||
} |
||||
|
||||
.query-container { |
||||
display: grid; |
||||
grid-template-columns: 11fr 1fr; |
||||
grid-column-gap: 5px; |
||||
} |
||||
|
||||
.query-error { |
||||
color: red; |
||||
} |
||||
|
||||
.logs-source__input { |
||||
border: 1px solid #a0a0a0; |
||||
height: auto; |
||||
resize: vertical; |
||||
min-height: 250px; |
||||
} |
||||
|
||||
.primary-button { |
||||
position: relative; |
||||
-webkit-box-align: center; |
||||
align-items: center; |
||||
padding: 0 8px; |
||||
border-radius: 2px; |
||||
line-height: 30px; |
||||
font-weight: 500; |
||||
white-space: nowrap; |
||||
background: #3871dc; |
||||
color: #fff; |
||||
border: 1px solid #0000; |
||||
} |
||||
|
||||
.primary-button:hover { |
||||
background: #2c5ab0; |
||||
color: #fff; |
||||
box-shadow: rgb(24 26 27 / 20%) 0 1px 2px; |
||||
} |
||||
|
||||
.panel-container { |
||||
border: 1px solid #24292e1f; |
||||
border-radius: 3px; |
||||
box-shadow: none; |
||||
margin-bottom: 10px; |
||||
padding: 10px 15px; |
||||
} |
||||
|
||||
.share-link-copied-notification { |
||||
color: #1b855e; |
||||
} |
||||
|
||||
.logs-source__header { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
} |
||||
|
||||
.panel-header { |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.last-stage-result { |
||||
display: grid; |
||||
grid-auto-flow: column; |
||||
grid-template-columns: 1.5fr 11fr; |
||||
} |
||||
|
||||
.last-stage-result:hover { |
||||
background: #fafafa; |
||||
} |
||||
|
||||
.line-index { |
||||
display: flex; |
||||
column-gap: 5px; |
||||
align-items: flex-start; |
||||
} |
||||
|
||||
.line-index__wrapper{ |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.line-cursor { |
||||
display: block; |
||||
width: 16px; |
||||
height: 16px; |
||||
} |
||||
|
||||
.expand-cursor { |
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' class='css-eio55b-topVerticalAlign'%3E%3Cpath d='M14.83,11.29,10.59,7.05a1,1,0,0,0-1.42,0,1,1,0,0,0,0,1.41L12.71,12,9.17,15.54a1,1,0,0,0,0,1.41,1,1,0,0,0,.71.29,1,1,0,0,0,.71-.29l4.24-4.24A1,1,0,0,0,14.83,11.29Z'%3E%3C/path%3E%3C/svg%3E"); |
||||
} |
||||
|
||||
.collapse-cursor { |
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' class='css-eio55b-topVerticalAlign'%3E%3Cpath d='M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z'%3E%3C/path%3E%3C/svg%3E"); |
||||
} |
||||
|
||||
.debug-result-row__explain { |
||||
display: grid; |
||||
justify-items: center; |
||||
grid-row-gap: 5px; |
||||
padding: 10px 5px 10px 25px; |
||||
border-bottom: 1px solid #24292e1f; |
||||
} |
||||
|
||||
.explain-section { |
||||
width: 100%; |
||||
border: 1px solid #24292e1f; |
||||
background-color: #f4f5f5; |
||||
border-radius: 2px; |
||||
|
||||
} |
||||
|
||||
.explain-section__header { |
||||
font-weight: 500; |
||||
border-bottom: 1px solid #24292e1f; |
||||
padding: 4px 8px; |
||||
} |
||||
|
||||
.explain-section__body { |
||||
padding: 8px; |
||||
display: grid; |
||||
grid-row-gap: 10px; |
||||
} |
||||
|
||||
|
||||
.input-box { |
||||
display: flex; |
||||
align-items: center; |
||||
border: 1px solid #a0a0a0; |
||||
padding-left: 0.5rem; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.input-box .prefix { |
||||
font-weight: 300; |
||||
color: #999; |
||||
} |
||||
|
||||
.input-box input { |
||||
flex-grow: 1; |
||||
background: #fff; |
||||
border: none; |
||||
outline: none; |
||||
padding: 0.5rem; |
||||
} |
||||
|
||||
.input-box:focus-within { |
||||
border-color: #777; |
||||
} |
||||
|
||||
.label-value { |
||||
border: 1px solid #777; |
||||
border-radius: 10px; |
||||
padding: 3px; |
||||
} |
||||
|
||||
.explain-section__row { |
||||
display: grid; |
||||
grid-template-columns: 3fr 9fr; |
||||
grid-column-gap: 10px; |
||||
} |
||||
|
||||
.explain-section__row-title { |
||||
word-break: break-word; |
||||
line-height: 2rem; |
||||
} |
||||
|
||||
.explain-section__row-body { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
column-gap: 10px; |
||||
row-gap: 5px; |
||||
font-weight: 500; |
||||
align-content: center; |
||||
} |
||||
|
||||
.filtered-out { |
||||
text-decoration: line-through; |
||||
font-weight: 300; |
||||
color: #999; |
||||
} |
||||
|
||||
.note-text { |
||||
font-weight: 300; |
||||
color: #999; |
||||
font-style: italic; |
||||
} |
||||
|
||||
.stage-expression { |
||||
color: #1f60c4; |
||||
} |
||||
|
||||
.important-text { |
||||
color: #ff0000 !important; |
||||
} |
Loading…
Reference in new issue