Website: Update links in query library and syntax highlighting on policy pages. (#23591)

Closes: #23516
Closes: #23517

Changes:
- Updated the headings on the /queries page to be clickable links
- Updated the query detail page to highlight osquery tables/columns in
queries
- Updated code blocks on the query details page to prevent the copy
button from covering the content in the code block.
This commit is contained in:
Eric 2024-11-07 15:49:16 -06:00 committed by GitHub
parent 1551157c23
commit c1ed73a8b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 121 additions and 18 deletions

View file

@ -34,6 +34,29 @@ module.exports = {
throw 'notFound';
}
// Find the related osquery table documentation for tables used in this query, and grab the keywordsForSyntaxHighlighting from each table used.
let allTablesInformation = _.filter(sails.config.builtStaticContent.markdownPages, (pageInfo)=>{
return _.startsWith(pageInfo.url, '/tables/');
});
// Get all the osquery table names, we'll use this list to determine which tables are used.
let allTableNames = _.pluck(allTablesInformation, 'title');
// Create an array of words in the query.
let queryWords = _.words(query.query, /[^ ]+/g);
let columnNamesForSyntaxHighlighting = [];
let tableNamesForSyntaxHighlighting = [];
// Get all of the words that appear in both arrays
let intersectionBetweenQueryWordsAndTableNames = _.intersection(queryWords, allTableNames);
// For each matched osquery table, add the keywordsForSyntaxHighlighting and the names of the tables used into two arrays.
for(let tableName of intersectionBetweenQueryWordsAndTableNames) {
let tableMentionedInThisQuery = _.find(sails.config.builtStaticContent.markdownPages, {title: tableName});
tableNamesForSyntaxHighlighting.push(tableMentionedInThisQuery.title);
let keyWordsForThisTable = tableMentionedInThisQuery.keywordsForSyntaxHighlighting;
columnNamesForSyntaxHighlighting = columnNamesForSyntaxHighlighting.concat(keyWordsForThisTable);
}
// Remove the table names from the array of column names to highlight.
columnNamesForSyntaxHighlighting = _.difference(columnNamesForSyntaxHighlighting, tableNamesForSyntaxHighlighting);
// Setting the meta title and description of this page using the query object, and falling back to a generic title or description if query.name or query.description are missing.
let pageTitleForMeta = query.name ? query.name + ' | Query details' : 'Query details';
let pageDescriptionForMeta = query.description ? query.description : 'View more information about a query in Fleet\'s standard query library';
@ -43,6 +66,8 @@ module.exports = {
queryLibraryYmlRepoPath: sails.config.builtStaticContent.queryLibraryYmlRepoPath,
pageTitleForMeta,
pageDescriptionForMeta,
columnNamesForSyntaxHighlighting,
tableNamesForSyntaxHighlighting,
algoliaPublicKey: sails.config.custom.algoliaPublicKey,
};

View file

@ -27,10 +27,58 @@ parasails.registerPage('query-detail', {
},
});
}
$('pre code').each((i, block) => {
window.hljs.highlightElement(block);
let columnNamesForThisQuery = [];
let tableNamesForThisQuery = [];
if(this.columnNamesForSyntaxHighlighting){
columnNamesForThisQuery = this.columnNamesForSyntaxHighlighting;
}
if(this.tableNamesForSyntaxHighlighting){
tableNamesForThisQuery = this.tableNamesForSyntaxHighlighting;
}
// Sorting the arrays of keywords by length to match larger keywords first.
columnNamesForThisQuery = columnNamesForThisQuery.sort((a,b)=>{
return a.length < b.length ? 1 : -1;
});
tableNamesForThisQuery = tableNamesForThisQuery.sort((a,b)=>{
return a.length < b.length ? 1 : -1;
});
(()=>{
$('pre code').each((i, block) => {
let tableNamesToHighlight = [];// Empty array to track the keywords that we will need to highlight
for(let tableName of tableNamesForThisQuery){// Going through the array of keywords for this table, if the entire word matches, we'll add it to the
for(let match of block.innerHTML.match(tableName+' ')||[]){
tableNamesToHighlight.push(match);
}
}
// Now iterate through the tableNamesToHighlight, replacing all matches in the elements innerHTML.
let replacementHMTL = block.innerHTML;
for(let keywordInExample of tableNamesToHighlight) {
let regexForThisExample = new RegExp(keywordInExample, 'g');
replacementHMTL = replacementHMTL.replace(regexForThisExample, '<span class="hljs-attr">'+_.trim(keywordInExample)+'</span> ');
}
$(block).html(replacementHMTL);
let columnNamesToHighlight = [];// Empty array to track the keywords that we will need to highlight
for(let columnName of columnNamesForThisQuery){// Going through the array of keywords for this table, if the entire word matches, we'll add it to the
for(let match of block.innerHTML.match(columnName)||[]){
columnNamesToHighlight.push(match);
}
}
for(let keywordInExample of columnNamesToHighlight) {
let regexForThisExample = new RegExp(keywordInExample, 'g');
replacementHMTL = replacementHMTL.replace(regexForThisExample, '<span class="hljs-string">'+_.trim(keywordInExample)+'</span>');
}
$(block).html(replacementHMTL);
window.hljs.highlightElement(block);
// After we've highlighted our keywords, we'll highlight the rest of the codeblock
// If this example is a single-line, we'll do some basic formatting to make it more human-readable.
if($(block)[0].innerText.match(/\n/gmi)){
$(block).addClass('has-linebreaks');
} else {
$(block).addClass('no-linebreaks');
}
});
})();
$('[purpose="copy-button"]').on('click', async function() {
let code = $(this).siblings('pre').find('code').text();
$(this).addClass('copied');

View file

@ -249,11 +249,11 @@
position: absolute;
top: 11px;
right: 10px;
// padding: 9px;
border-radius: 8px;
height: 32px;
width: 32px;
background: url('/images/icon-copy-16x16@2x.png');
background-color: #F9FAFC;
background-size: 14px 14px;
background-position: center;
background-repeat: no-repeat;
@ -273,7 +273,7 @@
pre {
width: 100%;
max-width: 100%;
padding: 16px 24px;
padding: 16px 44px 16px 24px;
border: 1px solid #E2E4EA;
background: #F9FAFC;
border-radius: 4px;
@ -281,6 +281,12 @@
margin-bottom: 24px;
code {
color: #515774;
&.has-linebreaks {
white-space: pre;
}
&.no-linebreaks {
white-space: normal;
}
font-family: 'Source Code Pro';
font-size: 14px;
font-weight: 400;
@ -288,8 +294,33 @@
.hljs-keyword { // SQL keywords (SELECT, FROM, WHERE, IN, etc.)
color: #AE6DDF;
}
[purpose='line-break']:not(:first-of-type)::before {
content: '\a';
}
.hljs-attr { // For table and column names
.hljs-keyword {
color: #FFF;
}
.hljs-string { // For words wrapped in quotation marks
color: #FFF;
}
color: #FFF;
background-color: #AE6DDF;
border-radius: 3px;
white-space: pre;
vertical-align: baseline;
span {
padding: 0;
}
}
.hljs-number {
color: #f5871f;
}
.hljs-string { // For words wrapped in quotation marks
color: #3DB67B;
color: #4fd061;
.hljs-keyword {
color: #4fd061;
}
}
background-color: @ui-off-white;
border: none;

View file

@ -103,10 +103,6 @@
height: 16px;
margin-right: 8px;
}
// span {
// padding-left: 15px;
// padding-right: 0px;
// }
background: #FFF;
&::placeholder {
font-size: 16px;
@ -188,11 +184,14 @@
max-width: 663px;
}
[purpose='policy-name'] {
color: #192147;
font-size: 20px;
font-weight: 800;
line-height: 24px;
margin-bottom: 8px;
a {
text-decoration: none;
color: #192147;
font-size: 20px;
font-weight: 800;
line-height: 24px;
margin-bottom: 8px;
}
}
[purpose='policy-description'] {
margin-top: 16px;

View file

@ -53,7 +53,7 @@
<div purpose="policy" class="d-flex flex-lg-row flex-column justify-content-between">
<div purpose="policy-name-and-description" class="d-flex flex-column">
<div class="d-flex flex-column">
<p purpose="policy-name"><%- policy.name %></p>
<p purpose="policy-name"><a href="/queries/<%- policy.slug%>"><%- policy.name %></a></p>
<% if(policy.tags.includes('premium')) {%><div purpose="premium-badge">PREMIUM</div><% } %>
<% if(policy.requiresMdm) {%><div purpose="requires-mdm-badge">mdm-required</div><% } %>
</div>
@ -77,7 +77,7 @@
<div purpose="policy" class="d-flex flex-lg-row flex-column justify-content-between">
<div purpose="policy-name-and-description" class="d-flex flex-column">
<div class="d-flex flex-column">
<p purpose="policy-name"><%- policy.name %></p>
<p purpose="policy-name"><a href="/queries/<%- policy.slug%>"><%- policy.name %></a></p>
<% if(policy.tags.includes('premium')) {%><div purpose="premium-badge">PREMIUM</div><% } %>
<% if(policy.requiresMdm) {%><div purpose="requires-mdm-badge">mdm-required</div><% } %>
</div>
@ -101,7 +101,7 @@
<div purpose="policy" class="d-flex flex-lg-row flex-column justify-content-between">
<div purpose="policy-name-and-description" class="d-flex flex-column">
<div class="d-flex flex-column">
<p purpose="policy-name"><%- policy.name %></p>
<p purpose="policy-name"><a href="/queries/<%- policy.slug%>"><%- policy.name %></a></p>
<% if(policy.tags.includes('premium')) {%><div purpose="premium-badge">PREMIUM</div><% } %>
<% if(policy.requiresMdm) {%><div purpose="requires-mdm-badge">mdm-required</div><% } %>
</div>