diff --git a/docs/01-Using-Fleet/standard-query-library/README.md b/docs/01-Using-Fleet/standard-query-library/README.md index 93de4746b7..2fd6495ad7 100644 --- a/docs/01-Using-Fleet/standard-query-library/README.md +++ b/docs/01-Using-Fleet/standard-query-library/README.md @@ -27,6 +27,7 @@ Want to add your own query? purpose: What is the goal of running your query? Ex. Detection remediation: Are there any remediation steps to resolve the detection triggered by your query? If not, insert "N/A." contributors: zwass,mike-j-thomas + tags: Keywords that can help users find other relevant queries, each tag should be seperated by a comma. (e.g., "foo,bar") ``` 2. Replace each field and submit a pull request to the fleetdm/fleet GitHub repository. diff --git a/website/assets/js/pages/query-library.page.js b/website/assets/js/pages/query-library.page.js index 0d55652358..402f055265 100644 --- a/website/assets/js/pages/query-library.page.js +++ b/website/assets/js/pages/query-library.page.js @@ -8,6 +8,7 @@ parasails.registerPage('query-library', { searchString: '', // The user input string to be searched against the query library selectedKind: 'all queries', // Initially set to all, the user may select a different option to filter queries by purpose (e.g., "all queries", "informational", "policies") selectedPlatform: 'all platforms', // Initially set to all, the user may select a different option to filter queries by platform (e.g., "all platforms", "macOS", "Windows", "Linux") + selectedTag: '', // Initially set to a blank string, the user may select a tag filter queries by. }, computed: { @@ -15,7 +16,8 @@ parasails.registerPage('query-library', { return this.queries.filter( (query) => this._isIncluded(query.platforms, this.selectedPlatform) && - this._isIncluded(query.kind, this.selectedKind) + this._isIncluded(query.kind, this.selectedKind) && + this._isIncluded(query.tags, this.selectedTag) ); }, @@ -50,6 +52,10 @@ parasails.registerPage('query-library', { this.selectedPlatform = platform; }, + clickSelectTag(tag) { + this.selectedTag = tag; + }, + clickCard: function (querySlug) { window.location = '/queries/' + querySlug; // we can trust the query slug is url-safe }, @@ -111,6 +117,11 @@ parasails.registerPage('query-library', { textToSearch += ', ' + normalize(contributor.name) + ', ' + normalize(contributor.handle); }); } + if (query.tags) { + query.tags.forEach((tag) => { + textToSearch += ', ' + normalize(tag); + }); + } return (searchTerms.some((term) => textToSearch.includes(term))); }); }, diff --git a/website/assets/styles/pages/query-detail.less b/website/assets/styles/pages/query-detail.less index f1eefaa432..481a423cdf 100644 --- a/website/assets/styles/pages/query-detail.less +++ b/website/assets/styles/pages/query-detail.less @@ -27,6 +27,14 @@ border-color: #E2E4EA; } + [purpose='query-tag'] { + font-size: 12px; + font-weight: 700; + padding: 4px 8px; + border-radius: 20px; + background-color: #E2E4EA; + } + [purpose='remediation'] { overflow-wrap: break-word; word-wrap: break-word; diff --git a/website/assets/styles/pages/query-library.less b/website/assets/styles/pages/query-library.less index 5c93fb38b3..825940cfcb 100644 --- a/website/assets/styles/pages/query-library.less +++ b/website/assets/styles/pages/query-library.less @@ -36,6 +36,25 @@ } } + [purpose='query-tag'] { + font-size: 12px; + font-weight: 700; + padding: 4px 8px; + border-radius: 20px; + background-color: #E2E4EA; + } + + [purpose='selected-tag'] { + border-bottom: 1px solid #e1e4e9; + padding: 16px 0; + margin-left: 30px; + margin-right: 30px; + p { + margin-bottom: 0; + line-height: 18px; + } + } + .input-group { &.search { width: 250px; @@ -205,7 +224,7 @@ border-radius: 8px; &:hover { .query-card { - background-color: #f1f0ff; + background-color: #F8F7FF; box-shadow: none; border: none; border-radius: 8px; @@ -221,6 +240,13 @@ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1); margin-bottom: 90px; width: 100%; + padding: 40px; + p { + margin-block-end: 0px; + } + a { + font-size: 16px; + } } .card-body { @@ -268,6 +294,14 @@ margin-bottom: 0px; } + [purpose='selected-tag'] { + margin-left: 30px; + margin-right: 30px; + [purpose='query-tag'] { + margin-top: 8px; + } + } + .results { margin-top: 16px; } diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index 303ec83f9c..0a75427424 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -30,6 +30,7 @@ module.exports = { let queriesWithProblematicResolutions = []; let queriesWithProblematicContributors = []; + let queriesWithProblematicTags = []; let queries = YAML.parseAllDocuments(yaml).map((yamlDocument)=>{ let query = yamlDocument.toJSON().spec; query.kind = yamlDocument.toJSON().kind; @@ -40,6 +41,25 @@ module.exports = { } else if (query.resolution === undefined) { query.resolution = 'N/A';// « We set this to a string here so that the data type is always string. We use N/A so folks can see there's no remediation and contribute if desired. } + if (query.tags) { + if(!_.isString(query.tags)) { + queriesWithProblematicTags.push(query); + } else { + // Splitting tags into an array to format them. + let tagsToFormat = query.tags.split(','); + let formattedTags = []; + for (let tag of tagsToFormat) { + if(tag !== '') {// « Ignoring any blank tags caused by trailing commas in the YAML. + // Formatting tags in sentence case, and removing any extra whitespace. + formattedTags.push(_.capitalize(_.trim(tag))); + } + } + // Removing any duplicate tags. + query.tags = _.uniq(formattedTags); + } + } else { + query.tags = []; // « if there are no tags, we set query.tags to an empty array so it is always the same data type. + } // GitHub usernames may only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen. if (!query.contributors || (query.contributors !== undefined && !_.isString(query.contributors)) || query.contributors.split(',').some((contributor) => contributor.match('^[^A-za-z0-9].*|[^A-Za-z0-9-]|.*[^A-za-z0-9]$'))) { @@ -52,6 +72,9 @@ module.exports = { if (queriesWithProblematicResolutions.length >= 1) { throw new Error('Failed parsing YAML for query library: The "resolution" of a query should either be absent (undefined) or a single string (not a list of strings). And "resolution" should only be present when a query\'s kind is "policy". But one or more queries have an invalid "resolution": ' + _.pluck(queriesWithProblematicResolutions, 'slug').sort()); }//• + if (queriesWithProblematicTags.length >= 1) { + throw new Error('Failed parsing YAML for query library: The "tags" of a query should either be absent (undefined) or a single string (not a list of strings). "tags" should be be be seperated by a comma. But one or more queries have invalid "tags": ' + _.pluck(queriesWithProblematicTags, 'slug').sort()); + } // Assert uniqueness of slugs. if (queries.length !== _.uniq(_.pluck(queries, 'slug')).length) { throw new Error('Failed parsing YAML for query library: Queries as currently named would result in colliding (duplicate) slugs. To resolve, rename the queries whose names are too similar. Note the duplicates: ' + _.pluck(queries, 'slug').sort()); diff --git a/website/views/pages/query-detail.ejs b/website/views/pages/query-detail.ejs index c9b8a0c47e..a32644c23e 100644 --- a/website/views/pages/query-detail.ejs +++ b/website/views/pages/query-detail.ejs @@ -41,6 +41,17 @@ +
Showing {{ selectedKind === 'query' ? 'informational queries' : selectedKind === 'policy' ? 'policies' : 'all queries'}} for {{selectedTag}}
+{{query.description}}
Want to add your own query? Please submit a pull request - over on GitHub - . + over on GitHub.