// ==UserScript==
// @name SentinelOne: PowerQuery Custom Menu
// @version 1
// @description Custom menu for threat hunting rules with a compact UI, cell copy on query page, and quick unpin feature.
// @author https://github.com/LasCC
// @match *://*.sentinelone.net/query*
// @match *://*.sentinelone.net/events*
// @downloadURL https://raw.githubusercontent.com/LasCC/SentinelOne-Userscript/refs/heads/master/userscript.js
// @updateURL https://raw.githubusercontent.com/LasCC/SentinelOne-Userscript/refs/heads/master/userscript.js
// @grant GM_xmlhttpRequest
// @icon https://www.google.com/s2/favicons?sz=64&domain=sentinelone.com
// ==/UserScript==
(function () {
"use strict";
const QUERIES_URL =
"https://raw.githubusercontent.com/LasCC/SentinelOne-Userscript/refs/heads/master/s1_powerquery_hunting.json";
const PINNED_QUERIES_KEY = "s1_pinned_hunting_queries";
let allFetchedQueries = [];
const listIconSVG = `
`;
const searchIconSVG = ``;
const starIconSVG = ``;
const copyIconSVG = ``;
const checkIconSVG = ``;
function getPinnedQueries() {
try {
const pinned = localStorage.getItem(PINNED_QUERIES_KEY);
return pinned ? JSON.parse(pinned) : [];
} catch (e) {
console.error("Could not parse pinned queries from localStorage", e);
return [];
}
}
function isQueryPinned(queryName) {
return getPinnedQueries().includes(queryName);
}
function togglePinQuery(queryName) {
let pinned = getPinnedQueries();
const index = pinned.indexOf(queryName);
if (index > -1) {
pinned.splice(index, 1);
} else {
pinned.push(queryName);
}
localStorage.setItem(PINNED_QUERIES_KEY, JSON.stringify(pinned));
renderPinnedQueriesSection();
document.dispatchEvent(new CustomEvent("pinnedQueryChange"));
}
function executeQuery(query) {
const queryTextarea = document.querySelector(
'[data-test-id="power-query-input"]'
);
const searchButton = document.querySelector(
'[data-test-id="power-query-search-button"]'
);
if (queryTextarea) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype,
"value"
).set;
nativeInputValueSetter.call(queryTextarea, query);
["input", "change"].forEach((eventType) =>
queryTextarea.dispatchEvent(new Event(eventType, { bubbles: true }))
);
queryTextarea.focus();
}
if (searchButton) searchButton.click();
showNotification("Query executed successfully!");
}
function showNotification(message) {
document
.querySelectorAll(".hunting-queries-notification")
.forEach((n) => n.remove());
const notification = document.createElement("div");
notification.className = "hunting-queries-notification";
notification.innerHTML = `
✓
${message}`;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add("hunting-queries-notification-fade");
setTimeout(() => notification.remove(), 300);
}, 2500);
}
function injectAndRenderPinnedQueriesSection() {
const powerQueryPage = document.querySelector(".Page.PowerQueries");
if (!powerQueryPage) return;
let container = document.getElementById("pinned-queries-container");
if (!container) {
container = document.createElement("div");
container.id = "pinned-queries-container";
container.className = "PowerQueries__PinnedSection";
const inputArea = document.querySelector(".PowerQueries__InputArea");
if (inputArea && inputArea.nextSibling) {
inputArea.parentNode.insertBefore(container, inputArea.nextSibling);
} else if (inputArea) {
inputArea.parentNode.appendChild(container);
}
}
renderPinnedQueriesSection();
}
function renderPinnedQueriesSection() {
const container = document.getElementById("pinned-queries-container");
if (!container || !Array.isArray(allFetchedQueries)) {
if (container) container.style.display = "none";
return;
}
const pinnedQueryNames = getPinnedQueries();
const pinnedQueries = allFetchedQueries.filter((q) =>
pinnedQueryNames.includes(q.name)
);
pinnedQueries.sort((a, b) => a.name.localeCompare(b.name));
container.innerHTML = "";
if (pinnedQueries.length === 0) {
container.style.display = "none";
return;
}
container.style.display = "block";
const list = document.createElement("div");
list.className = "pinned-queries-list";
container.appendChild(list);
const header = document.createElement("div");
header.className = "pinned-queries-header";
header.innerHTML = `
Pinned
`;
list.appendChild(header);
pinnedQueries.forEach((queryObj) => {
const pinButtonWrapper = document.createElement("div");
pinButtonWrapper.className = "pinned-query-btn";
pinButtonWrapper.title = `Run query: ${queryObj.name}`;
const nameSpan = document.createElement("span");
nameSpan.textContent = queryObj.name;
pinButtonWrapper.appendChild(nameSpan);
const unpinButton = document.createElement("button");
unpinButton.className = "unpin-button";
unpinButton.innerHTML = "×";
unpinButton.title = "Unpin query";
unpinButton.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
togglePinQuery(queryObj.name);
});
pinButtonWrapper.appendChild(unpinButton);
pinButtonWrapper.addEventListener("click", (e) => {
e.preventDefault();
executeQuery(queryObj.query);
});
list.appendChild(pinButtonWrapper);
});
}
function addCustomQueryButton(predefinedQueries) {
const toolbar = document.querySelector(
".Toolbar.Toolbar.PowerQueries__ControlsToolbar"
);
if (!toolbar) return;
const tableButtonToolbarItem = toolbar
.querySelector(
'.Toolbar__Item.Toolbar__Item > div > [data-test-id="graph-style-dropdown"]'
)
?.closest(".Toolbar__Item.Toolbar__Item");
if (!tableButtonToolbarItem) return;
if (document.getElementById("custom-queries-button-container")) return;
addCustomStyles();
const customQueriesContainer = document.createElement("div");
customQueriesContainer.id = "custom-queries-button-container";
customQueriesContainer.className = "Toolbar__Item Toolbar__Item";
const dropdownDiv = document.createElement("div");
dropdownDiv.className = "dropdown hunting-queries-dropdown";
dropdownDiv.setAttribute("tabindex", "-1");
const button = document.createElement("button");
button.id = "custom-queries-button";
button.type = "button";
button.className =
"Button Button--Secondary Dropdown__Button btn btn-secondary btn-sm hunting-queries-btn";
button.setAttribute("data-test-id", "custom-queries-button");
button.setAttribute("aria-haspopup", "true");
button.setAttribute("aria-expanded", "false");
button.setAttribute("role", "button");
button.innerHTML = `
${listIconSVG}
Hunting Queries
`;
const dropdownMenu = document.createElement("div");
dropdownMenu.className = "Dropdown__Menu dropdown-menu hunting-queries-menu";
dropdownMenu.setAttribute("role", "menu");
const dropdownInner = document.createElement("div");
dropdownInner.className = "dropdown-menu__inner";
dropdownInner.setAttribute("data-test-id", "custom-queries-menu");
const dropdownHeader = document.createElement("div");
dropdownHeader.className = "hunting-queries-header";
dropdownHeader.innerHTML = `
${listIconSVG}
Hunting Queries
(0)
`;
const searchContainer = document.createElement("div");
searchContainer.className = "hunting-queries-search-container";
const searchWrapper = document.createElement("div");
searchWrapper.className = "hunting-queries-search-wrapper";
const searchIcon = document.createElement("div");
searchIcon.className = "hunting-queries-search-icon";
searchIcon.innerHTML = searchIconSVG;
const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.className = "hunting-queries-search";
searchInput.placeholder = "Search queries...";
searchInput.setAttribute("aria-label", "Search hunting queries");
const clearButton = document.createElement("button");
clearButton.className = "hunting-queries-clear";
clearButton.innerHTML = "×";
clearButton.title = "Clear search";
clearButton.style.display = "none";
searchWrapper.appendChild(searchIcon);
searchWrapper.appendChild(searchInput);
searchWrapper.appendChild(clearButton);
searchContainer.appendChild(searchWrapper);
const tabsContainer = document.createElement("div");
tabsContainer.className = "hunting-queries-tabs";
const tabsScroll = document.createElement("div");
tabsScroll.className = "hunting-queries-tabs-scroll";
const navigationDiv = document.createElement("div");
navigationDiv.className = "hunting-queries-list";
navigationDiv.setAttribute("role", "navigation");
const loadingDiv = document.createElement("div");
loadingDiv.className = "hunting-queries-loading";
loadingDiv.innerHTML = `
Loading queries...
`;
const emptyState = document.createElement("div");
emptyState.className = "hunting-queries-empty";
emptyState.innerHTML = `
🔍
No queries found
Try adjusting your search or filters
`;
emptyState.style.display = "none";
let activeCategory = "All";
let isLoading = true;
const categories = ["All", "Pinned"];
if (Array.isArray(predefinedQueries)) {
const uniqueCategories = new Set(
predefinedQueries
.map((q) => q.category)
.filter(Boolean)
.filter((cat) => cat.trim() !== "")
);
categories.push(...Array.from(uniqueCategories).sort());
isLoading = false;
}
function updateQueryCount(count) {
const countElement = document.getElementById("query-count");
if (countElement) {
countElement.textContent = count;
}
}
function renderTabs() {
tabsScroll.innerHTML = "";
categories.forEach((category) => {
const tabButton = document.createElement("button");
tabButton.className = `hunting-queries-tab ${
category === activeCategory ? "active" : ""
}`;
tabButton.setAttribute("data-category", category);
if (category === "Pinned") {
tabButton.innerHTML = `${starIconSVG} Pinned`;
} else {
tabButton.innerHTML = `${category}`;
}
let count = 0;
if (Array.isArray(predefinedQueries)) {
if (category === "Pinned") {
const pinnedNames = getPinnedQueries();
count = predefinedQueries.filter((q) =>
pinnedNames.includes(q.name)
).length;
} else if (category === "All") {
count = predefinedQueries.length;
} else {
count = predefinedQueries.filter(
(q) => q.category === category
).length;
}
} else if (category === "Pinned") {
count = getPinnedQueries().length;
}
if (count > 0) {
const badge = document.createElement("span");
badge.className = "hunting-queries-tab-badge";
badge.textContent = count;
tabButton.appendChild(badge);
}
tabButton.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
setActiveCategory(category);
});
tabsScroll.appendChild(tabButton);
});
}
function setActiveCategory(category) {
activeCategory = category;
document
.querySelectorAll(".hunting-queries-tab")
.forEach((tab) => tab.classList.remove("active"));
document
.querySelector(`[data-category="${category}"]`)
?.classList.add("active");
renderQueryItems(searchInput.value);
}
function renderQueryItems(searchTerm = "") {
if (isLoading) {
navigationDiv.innerHTML = "";
navigationDiv.appendChild(loadingDiv);
updateQueryCount(0);
return;
}
navigationDiv.innerHTML = "";
emptyState.style.display = "none";
const lowerSearchTerm = searchTerm.toLowerCase().trim();
const pinnedQueryNames = getPinnedQueries();
if (!Array.isArray(predefinedQueries)) {
const errorItem = document.createElement("div");
errorItem.className = "hunting-queries-error";
errorItem.innerHTML = `
⚠️
Failed to load queries
Please try refreshing the page
`;
navigationDiv.appendChild(errorItem);
updateQueryCount(0);
return;
}
let filteredQueries = predefinedQueries.filter((queryObj) => {
const matchesCategory =
activeCategory === "All" ||
(activeCategory === "Pinned" &&
pinnedQueryNames.includes(queryObj.name)) ||
queryObj.category === activeCategory;
const matchesSearch =
!lowerSearchTerm ||
queryObj.name.toLowerCase().includes(lowerSearchTerm) ||
(queryObj.description &&
queryObj.description.toLowerCase().includes(lowerSearchTerm));
return matchesCategory && matchesSearch;
});
filteredQueries.sort((a, b) => {
const aIsPinned = pinnedQueryNames.includes(a.name);
const bIsPinned = pinnedQueryNames.includes(b.name);
if (aIsPinned && !bIsPinned) return -1;
if (!aIsPinned && bIsPinned) return 1;
return a.name.localeCompare(b.name);
});
updateQueryCount(filteredQueries.length);
if (filteredQueries.length === 0) {
navigationDiv.appendChild(emptyState);
emptyState.style.display = "block";
return;
}
const groupedQueries = {};
filteredQueries.forEach((query) => {
const category = query.category || "Uncategorized";
if (!groupedQueries[category]) groupedQueries[category] = [];
groupedQueries[category].push(query);
});
Object.entries(groupedQueries).forEach(([category, queries]) => {
if (
activeCategory === "All" &&
Object.keys(groupedQueries).length > 1
) {
const categoryHeader = document.createElement("div");
categoryHeader.className = "hunting-queries-category-header";
categoryHeader.textContent = category;
navigationDiv.appendChild(categoryHeader);
}
queries.forEach((queryObj) => {
const queryItem = document.createElement("div");
queryItem.className = "hunting-queries-item";
queryItem.setAttribute("data-query", queryObj.query);
const queryContent = document.createElement("div");
queryContent.className = "hunting-queries-item-content";
const queryName = document.createElement("div");
queryName.className = "hunting-queries-item-name";
queryName.textContent = queryObj.name;
const queryMeta = document.createElement("div");
queryMeta.className = "hunting-queries-item-meta";
if (queryObj.description) {
const description = document.createElement("div");
description.className = "hunting-queries-item-description";
description.textContent = queryObj.description;
queryMeta.appendChild(description);
}
if (queryObj.category && activeCategory === "All") {
const categoryTag = document.createElement("span");
categoryTag.className = "hunting-queries-item-category";
categoryTag.textContent = queryObj.category;
queryMeta.appendChild(categoryTag);
}
queryContent.appendChild(queryName);
if (queryMeta.children.length > 0) {
queryContent.appendChild(queryMeta);
}
const queryActions = document.createElement("div");
queryActions.className = "hunting-queries-item-actions";
const pinButton = document.createElement("button");
pinButton.className = "hunting-queries-pin-btn";
pinButton.innerHTML = starIconSVG;
if (isQueryPinned(queryObj.name)) {
pinButton.classList.add("pinned");
pinButton.title = "Unpin query";
} else {
pinButton.title = "Pin query";
}
pinButton.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
togglePinQuery(queryObj.name);
});
const useButton = document.createElement("button");
useButton.className = "hunting-queries-use-btn";
useButton.innerHTML = "Use";
useButton.title = "Insert and run this query";
queryActions.appendChild(pinButton);
queryActions.appendChild(useButton);
queryItem.appendChild(queryContent);
queryItem.appendChild(queryActions);
if (lowerSearchTerm) {
highlightSearchTerm(queryName, lowerSearchTerm);
if (queryObj.description) {
const descElement = queryContent.querySelector(
".hunting-queries-item-description"
);
if (descElement) {
highlightSearchTerm(descElement, lowerSearchTerm);
}
}
}
const handleQuerySelection = (e) => {
e.preventDefault();
e.stopPropagation();
executeQuery(queryObj.query);
closeDropdown();
};
queryItem.addEventListener("click", handleQuerySelection);
useButton.addEventListener("click", handleQuerySelection);
queryItem.setAttribute("tabindex", "0");
queryItem.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleQuerySelection(e);
}
});
navigationDiv.appendChild(queryItem);
});
});
}
function highlightSearchTerm(element, searchTerm) {
const text = element.textContent;
const regex = new RegExp(
`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
"gi"
);
element.innerHTML = text.replace(
regex,
'$1'
);
}
function closeDropdown() {
dropdownMenu.classList.remove("show");
button.setAttribute("aria-expanded", "false");
button.classList.remove("active");
}
function openDropdown() {
dropdownMenu.classList.add("show");
button.setAttribute("aria-expanded", "true");
button.classList.add("active");
setTimeout(() => searchInput.focus(), 100);
}
tabsContainer.appendChild(tabsScroll);
dropdownInner.appendChild(dropdownHeader);
dropdownInner.appendChild(searchContainer);
dropdownInner.appendChild(tabsContainer);
dropdownInner.appendChild(navigationDiv);
dropdownMenu.appendChild(dropdownInner);
dropdownDiv.appendChild(button);
dropdownDiv.appendChild(dropdownMenu);
customQueriesContainer.appendChild(dropdownDiv);
tableButtonToolbarItem.parentNode.insertBefore(
customQueriesContainer,
tableButtonToolbarItem.nextSibling
);
searchInput.addEventListener("input", (e) => {
const value = e.target.value;
clearButton.style.display = value ? "block" : "none";
renderQueryItems(value);
});
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeDropdown();
});
clearButton.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
searchInput.value = "";
clearButton.style.display = "none";
renderQueryItems("");
searchInput.focus();
});
button.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const isExpanded = button.getAttribute("aria-expanded") === "true";
if (isExpanded) closeDropdown();
else {
openDropdown();
renderTabs();
renderQueryItems(searchInput.value);
}
});
document.addEventListener("click", (e) => {
if (
!dropdownDiv.contains(e.target) &&
dropdownMenu.classList.contains("show")
)
closeDropdown();
});
document.addEventListener("pinnedQueryChange", () => {
if (dropdownMenu.classList.contains("show")) {
renderTabs();
renderQueryItems(searchInput.value);
}
});
renderTabs();
renderQueryItems();
}
function addCellCopyButtons(cells) {
if (!window.location.href.includes("/query")) {
return;
}
cells.forEach((cell) => {
if (cell.textContent.trim() === "" || cell.querySelector(".cell-copy-button")) {
return;
}
cell.classList.add("copy-button-added");
const button = document.createElement("button");
button.className = "cell-copy-button";
button.innerHTML = copyIconSVG;
button.title = "Copy cell content";
button.addEventListener("click", (e) => {
e.stopPropagation();
const cellClone = cell.cloneNode(true);
const buttonInClone = cellClone.querySelector(".cell-copy-button");
if (buttonInClone) {
buttonInClone.remove();
}
const textToCopy = cellClone.textContent.trim();
navigator.clipboard
.writeText(textToCopy)
.then(() => {
button.innerHTML = checkIconSVG;
button.title = "Copied!";
button.classList.add("copied");
setTimeout(() => {
button.innerHTML = copyIconSVG;
button.title = "Copy cell content";
button.classList.remove("copied");
}, 2000);
})
.catch((err) => {
console.error("Failed to copy text: ", err);
button.title = "Failed to copy!";
setTimeout(() => {
button.title = "Copy cell content";
}, 2000);
});
});
cell.appendChild(button);
});
}
function addCustomStyles() {
if (document.getElementById("hunting-queries-styles")) return;
const styles = document.createElement("style");
styles.id = "hunting-queries-styles";
styles.textContent = `
/* --- Compact Pinned Queries Section --- */
.PowerQueries__PinnedSection {
padding: 6px var(--s1-distance-5);
border-top: 1px solid var(--s1-N-20-color);
border-bottom: 1px solid var(--s1-N-20-color);
background: var(--s1-N-5-color);
display: none; /* Hidden by default */
}
.pinned-queries-list { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pinned-queries-header {
display: flex; align-items: center; gap: 6px;
font-size: 11px; font-weight: 600; color: var(--s1-N-70-color);
text-transform: uppercase; letter-spacing: 0.5px;
margin-right: 8px;
}
.pinned-queries-header .star-icon { color: var(--s1-P-50-color); }
.pinned-query-btn {
position: relative;
display: inline-flex; align-items: center;
font-size: 10px; padding: 2px 8px;
background-color: var(--s1-N-15-color);
border: 1px solid var(--s1-N-25-color);
border-radius: var(--s1-border-radius-3);
color: var(--s1-N-80-color);
cursor: pointer;
transition: all 0.2s ease;
}
.pinned-query-btn:hover {
transform: translateY(-1px);
box-shadow: var(--s1-shadow-2);
background-color: var(--s1-N-20-color);
border-color: var(--s1-N-30-color);
}
.unpin-button {
position: absolute;
top: -6px; right: -6px;
width: 16px; height: 16px;
border-radius: 50%;
background: var(--s1-N-80-color);
color: var(--s1-N-0-color);
border: 1px solid var(--s1-N-0-color);
display: flex; align-items: center; justify-content: center;
font-size: 14px; line-height: 1;
cursor: pointer;
opacity: 0;
transform: scale(0.5);
transition: all 0.15s ease-out;
z-index: 1;
}
.pinned-query-btn:hover .unpin-button {
opacity: 1;
transform: scale(1);
}
.unpin-button:hover { background: var(--s1-R-50-color); }
/* --- Main Dropdown & Button --- */
.hunting-queries-dropdown { position: relative; }
.hunting-queries-btn { transition: all 0.2s ease; position: relative; }
.hunting-queries-btn:hover { transform: translateY(-1px); box-shadow: var(--s1-shadow-6); }
.hunting-queries-arrow { transition: transform 0.2s ease; }
.hunting-queries-btn.active .hunting-queries-arrow { transform: rotate(180deg); }
.hunting-queries-menu {
position: absolute; top: calc(100% + 4px); right: 0;
min-width: 450px; max-width: 550px; max-height: 65vh;
z-index: 1050; display: none; background: var(--s1-N-0-color);
border: 1px solid var(--s1-N-20-color); border-radius: var(--s1-distance-2);
box-shadow: var(--s1-shadow-16); overflow: hidden; animation: hunting-queries-fadeIn 0.2s ease;
font-family: var(--s1-font-family);
}
.hunting-queries-menu.show { display: block; }
@keyframes hunting-queries-fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } }
/* --- Compact Dropdown Header --- */
.hunting-queries-header {
display: flex; align-items: center; gap: 8px;
padding: 8px var(--s1-distance-5);
background: var(--s1-N-10-color);
border-bottom: 1px solid var(--s1-N-20-color);
}
.hunting-queries-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; color: var(--s1-N-100-color); }
.hunting-queries-title img { filter: none; }
.hunting-queries-count { font-size: 12px; color: var(--s1-N-60-color); font-weight: 400; }
/* --- Compact Search & Tabs --- */
.hunting-queries-search-container { padding: 8px var(--s1-distance-5); background: var(--s1-N-10-color); border-bottom: 1px solid var(--s1-N-20-color); }
.hunting-queries-search-wrapper { position: relative; display: flex; align-items: center; }
.hunting-queries-search-icon { position: absolute; left: 10px; color: var(--s1-N-50-color); z-index: 1; }
.hunting-queries-search {
width: 100%; padding: 6px 10px 6px 34px;
border: 1px solid var(--s1-N-30-color); border-radius: var(--s1-border-radius-3); font-size: 13px;
background: var(--s1-N-0-color); color: var(--s1-N-100-color); transition: border-color 0.2s ease;
}
.hunting-queries-search:focus { outline: none; border-color: var(--s1-P-50-color); box-shadow: 0 0 0 2px color-mix(in srgb, var(--s1-P-50-color) 20%, transparent); }
.hunting-queries-clear { position: absolute; right: 4px; background: none; border: none; font-size: 18px; color: var(--s1-N-50-color); cursor: pointer; padding: 4px; border-radius: 50%; transition: all 0.2s ease; }
.hunting-queries-clear:hover { background: var(--s1-N-20-color); color: var(--s1-N-70-color); }
.hunting-queries-tabs { background: var(--s1-N-10-color); border-bottom: 1px solid var(--s1-N-20-color); overflow: hidden; }
.hunting-queries-tabs-scroll { display: flex; padding: 8px var(--s1-distance-5); overflow-x: auto; gap: 6px; scrollbar-width: none; -ms-overflow-style: none; }
.hunting-queries-tabs-scroll::-webkit-scrollbar { display: none; }
.hunting-queries-tab {
display: flex; align-items: center; gap: 6px; padding: 3px 10px;
border: 1px solid var(--s1-N-30-color); border-radius: 16px; background: var(--s1-N-0-color); color: var(--s1-N-60-color);
font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; white-space: nowrap;
}
.hunting-queries-tab:hover { border-color: var(--s1-P-50-color); color: var(--s1-P-50-color); }
.hunting-queries-tab.active { background: var(--s1-P-50-color); color: var(--s1-const-N-0-color, #fff); border-color: var(--s1-P-50-color); }
.hunting-queries-tab-content { display: flex; align-items: center; gap: 4px; }
.hunting-queries-tab-badge {
background: var(--s1-N-20-color); color: var(--s1-N-70-color); font-size: 9px; font-weight: 600;
padding: 1px 5px; border-radius: 8px; margin-left: 4px;
}
.hunting-queries-tab.active .hunting-queries-tab-badge { background: rgba(0,0,0,0.2); color: var(--s1-const-N-0-color, #fff); }
/* --- Denser Query List --- */
.hunting-queries-list { max-height: 350px; overflow-y: auto; padding: 4px 0; background: var(--s1-N-0-color); }
.hunting-queries-category-header {
padding: 6px var(--s1-distance-5) 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
background: var(--s1-N-10-color); color: var(--s1-N-70-color); border-bottom: 1px solid var(--s1-N-20-color); margin-bottom: 4px;
}
.hunting-queries-item { display: flex; align-items: center; justify-content: space-between; padding: 6px var(--s1-distance-5); cursor: pointer; transition: background-color 0.2s ease, border-left-color 0.2s ease; border-left: 3px solid transparent; }
.hunting-queries-item:hover { background: var(--s1-N-10-color); border-left-color: var(--s1-P-50-color); }
.hunting-queries-item:focus, .hunting-queries-item:focus-within { outline: none; background: var(--s1-N-15-color); border-left-color: var(--s1-P-50-color); }
.hunting-queries-item-content { flex: 1; min-width: 0; }
.hunting-queries-item-name { font-size: 13px; font-weight: 500; color: var(--s1-N-100-color); line-height: 1.3; }
.hunting-queries-item-meta { display: flex; flex-direction: column; gap: 4px; margin-top: 2px; }
.hunting-queries-item-description { font-size: 11px; color: var(--s1-N-60-color); line-height: 1.3; }
.hunting-queries-item-category { display: inline-block; font-size: 10px; background: var(--s1-N-15-color); color: var(--s1-N-70-color); padding: 1px 5px; border-radius: var(--s1-border-radius-3); font-weight: 500; width: fit-content; }
.hunting-queries-item-actions { display: flex; align-items: center; opacity: 0; transition: opacity 0.2s ease; gap: 6px; }
.hunting-queries-item:hover .hunting-queries-item-actions, .hunting-queries-item:focus-within .hunting-queries-item-actions { opacity: 1; }
.hunting-queries-pin-btn { background: none; border: none; cursor: pointer; padding: 4px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--s1-N-40-color); transition: all 0.2s ease; }
.hunting-queries-pin-btn:hover { background: var(--s1-N-15-color); color: var(--s1-P-50-color); }
.hunting-queries-pin-btn .star-icon { fill: none; stroke: currentColor; }
.hunting-queries-pin-btn.pinned .star-icon { color: var(--s1-P-50-color); fill: var(--s1-P-50-color); stroke: var(--s1-P-50-color); }
.hunting-queries-pin-btn.pinned:hover { color: var(--s1-P-40-color); }
.hunting-queries-use-btn { background: var(--s1-P-50-color); color: var(--s1-const-N-0-color, #fff); border: none; padding: 3px 8px; border-radius: var(--s1-border-radius-3); font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; }
.hunting-queries-use-btn:hover { background: var(--s1-P-40-color); }
.hunting-queries-highlight { background: var(--s1-N-20-color); color: var(--s1-N-100-color); padding: 0 2px; border-radius: 2px; font-weight: 500; }
/* --- Cell Copy Button (Safe Absolute Positioning) --- */
.BaseTable__row-cell {
position: relative;
}
.cell-copy-button {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
background: var(--s1-N-10-color); border: 1px solid var(--s1-N-30-color);
border-radius: 4px; padding: 3px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
opacity: 0;
transition: opacity 0.15s ease, background-color 0.15s ease;
z-index: 2;
}
.BaseTable__row-cell:hover .cell-copy-button { opacity: 1; }
.cell-copy-button:hover { background: var(--s1-N-20-color); }
.cell-copy-button svg { width: 14px; height: 14px; color: var(--s1-N-70-color); transition: color 0.15s ease; }
.cell-copy-button:hover svg { color: var(--s1-N-100-color); }
.cell-copy-button.copied svg { color: var(--s1-G-50-color); }
/* --- Utility & Notification --- */
.hunting-queries-loading, .hunting-queries-error, .hunting-queries-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px var(--s1-distance-5); text-align: center; }
.hunting-queries-spinner { width: 20px; height: 20px; border: 2px solid var(--s1-N-70-color); border-top-color: var(--s1-P-50-color); border-radius: 50%; animation: hunting-queries-spin 1s linear infinite; margin-bottom: 12px; }
@keyframes hunting-queries-spin { to { transform: rotate(360deg); } }
.hunting-queries-empty-icon, .hunting-queries-error-icon { font-size: 28px; margin-bottom: 12px; color: var(--s1-N-60-color); }
.hunting-queries-empty-title, .hunting-queries-error-title { font-size: 13px; font-weight: 600; color: var(--s1-N-70-color); margin-bottom: 4px; }
.hunting-queries-empty-subtitle, .hunting-queries-error-subtitle { font-size: 11px; color: var(--s1-N-50-color); }
.hunting-queries-notification {
position: fixed; bottom: var(--s1-distance-5); right: var(--s1-distance-5);
background: var(--s1-N-0-color); border: 1px solid var(--s1-N-20-color); border-radius: var(--s1-border-radius-3);
padding: 8px 12px; z-index: 10000; animation: hunting-queries-slideIn 0.3s ease;
box-shadow: var(--s1-shadow-6); display: flex; align-items: center; gap: 8px; max-width: 220px; font-size: 12px;
}
.hunting-queries-notification-icon { width: 14px; height: 14px; border-radius: 50%; background: var(--s1-G-50-color); color: var(--s1-const-N-0-color, #fff); display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: bold; flex-shrink: 0; }
.hunting-queries-notification-text { font-weight: 500; color: var(--s1-N-100-color); }
.hunting-queries-notification-fade { animation: hunting-queries-fadeOut 0.3s ease; }
@keyframes hunting-queries-slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes hunting-queries-fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
`;
document.head.appendChild(styles);
}
function fetchQueriesAndInject() {
GM_xmlhttpRequest({
method: "GET",
url: QUERIES_URL,
onload: function (response) {
try {
const queries = JSON.parse(response.responseText);
allFetchedQueries = queries;
addCustomQueryButton(queries);
injectAndRenderPinnedQueriesSection();
} catch (e) {
console.error("Error parsing queries JSON:", e);
allFetchedQueries = null;
addCustomQueryButton(null);
injectAndRenderPinnedQueriesSection();
}
},
onerror: function (error) {
console.error("Error fetching queries:", error);
allFetchedQueries = null;
addCustomQueryButton(null);
injectAndRenderPinnedQueriesSection();
},
});
}
const observer = new MutationObserver((mutations, obs) => {
if (!document.getElementById("custom-queries-button-container")) {
const powerQueryPage = document.querySelector(
'.Page.PowerQueries[data-test-id="power-query-page"]'
);
if (powerQueryPage) {
const toolbar = powerQueryPage.querySelector(
".Toolbar.Toolbar.PowerQueries__ControlsToolbar"
);
const tableButton = powerQueryPage.querySelector(
'[data-test-id="graph-style-dropdown"]'
);
if (toolbar && tableButton) {
fetchQueriesAndInject();
}
}
}
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) {
if (node.matches(".BaseTable__row-cell")) {
addCellCopyButtons([node]);
}
const newCells = node.querySelectorAll(
".BaseTable__row-cell:not(.copy-button-added)"
);
if (newCells.length > 0) {
addCellCopyButtons(newCells);
}
}
}
}
});
const appRoot = document.getElementById("root");
if (appRoot) {
observer.observe(appRoot, { childList: true, subtree: true });
}
})();