Tidying the repo
This commit is contained in:
11
Firefox/Code/INSTALL.md
Normal file
11
Firefox/Code/INSTALL.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Installing SimplyReports SQL Extractor (SiReS Ex)
|
||||
|
||||
1. Download ```sires-ex.xpi```
|
||||
2. Drag that file into Firefox
|
||||
3. Click "Add" when prompted
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If the icon doesn't appear, check the Extensions page (`about:addons`)
|
||||
- Make sure the target page has an input with id or name "SQLStatementHide"
|
||||
- Check browser console for any error messages
|
||||
18
Firefox/Code/README.md
Normal file
18
Firefox/Code/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# SimplyReports SQL Extractor (SiReS Ex)
|
||||
|
||||
The SimplyReports SQL Extractor is a Firefox add-on that will pull the SQL query from a SimplyReports results page. After extracting the query, it allows you to copy that query to the clipboard or save it as a file.
|
||||
|
||||
## Features
|
||||
|
||||
- Visual indicator when a SimplyReports SQL query is found on a page
|
||||
- Copy extracted SQL to clipboard
|
||||
- Save SQL as .sql file with custom filename
|
||||
- Basic SQL formatting for better readability
|
||||
- Auto-extraction when opening the extension popup
|
||||
|
||||
## Usage
|
||||
|
||||
1. Build a report in SimplyReports.
|
||||
2. Run the report.
|
||||
3. On the results page, you should see a green indicator on the top right of the page that indicates that SiReS Ex found a SimplyReports query in your results page.
|
||||
4. Click the add-on's icon and you'll be prompted to copy the query to the clipboard or save the query to a file.
|
||||
29
Firefox/Code/background.js
Normal file
29
Firefox/Code/background.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// background.js - Background script for handling downloads
|
||||
|
||||
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === "downloadSQL") {
|
||||
const { sqlQuery, filename } = request;
|
||||
|
||||
// Create blob with SQL content
|
||||
const blob = new Blob([sqlQuery], { type: 'text/sql' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Generate filename if not provided
|
||||
const finalFilename = filename || `sql_query_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.sql`;
|
||||
|
||||
// Download the file
|
||||
browser.downloads.download({
|
||||
url: url,
|
||||
filename: finalFilename,
|
||||
saveAs: true
|
||||
}).then(() => {
|
||||
URL.revokeObjectURL(url);
|
||||
sendResponse({ success: true });
|
||||
}).catch((error) => {
|
||||
console.error('Download failed:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
|
||||
return true; // Keep the message channel open for async response
|
||||
}
|
||||
});
|
||||
89
Firefox/Code/content.js
Normal file
89
Firefox/Code/content.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// content.js - Content script that runs on web pages
|
||||
|
||||
function extractSQLQuery() {
|
||||
// Look for the specific input element
|
||||
const sqlInput = document.getElementById('SQLStatementHide');
|
||||
|
||||
if (sqlInput) {
|
||||
const sqlQuery = sqlInput.value;
|
||||
if (sqlQuery && sqlQuery.trim()) {
|
||||
return cleanupSQLQuery(sqlQuery.trim());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: look for any input with name="SQLStatementHide"
|
||||
const sqlInputByName = document.querySelector('input[name="SQLStatementHide"]');
|
||||
if (sqlInputByName) {
|
||||
const sqlQuery = sqlInputByName.value;
|
||||
if (sqlQuery && sqlQuery.trim()) {
|
||||
return cleanupSQLQuery(sqlQuery.trim());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function cleanupSQLQuery(sqlQuery) {
|
||||
// Fix double single quotes around dates/values (''2017-10-23'' becomes '2017-10-23')
|
||||
// This handles the common issue where systems incorrectly double-escape quotes
|
||||
return sqlQuery.replace(/''/g, "'");
|
||||
}
|
||||
|
||||
// Listen for messages from popup
|
||||
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === "extractSQL") {
|
||||
const sqlQuery = extractSQLQuery();
|
||||
sendResponse({
|
||||
success: sqlQuery !== null,
|
||||
sqlQuery: sqlQuery,
|
||||
url: window.location.href,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Optional: Add a visual indicator when SQL is found
|
||||
function addVisualIndicator() {
|
||||
const sqlInput = document.getElementById('SQLStatementHide') ||
|
||||
document.querySelector('input[name="SQLStatementHide"]');
|
||||
|
||||
if (sqlInput && sqlInput.value && sqlInput.value.trim()) {
|
||||
// Add a small visual indicator that SQL was found
|
||||
if (!document.getElementById('sql-extractor-indicator')) {
|
||||
const indicator = document.createElement('div');
|
||||
indicator.id = 'sql-extractor-indicator';
|
||||
indicator.style.cssText = `
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
z-index: 10000;
|
||||
font-family: Arial, sans-serif;
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
`;
|
||||
indicator.textContent = 'SQL Query Found';
|
||||
document.body.appendChild(indicator);
|
||||
|
||||
// Remove indicator after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('sql-extractor-indicator')) {
|
||||
document.body.removeChild(indicator);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run indicator check when page loads
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', addVisualIndicator);
|
||||
} else {
|
||||
addVisualIndicator();
|
||||
}
|
||||
BIN
Firefox/Code/icon-128.png
Normal file
BIN
Firefox/Code/icon-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
Firefox/Code/icon-16.png
Normal file
BIN
Firefox/Code/icon-16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 577 B |
BIN
Firefox/Code/icon-32.png
Normal file
BIN
Firefox/Code/icon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Firefox/Code/icon-48.png
Normal file
BIN
Firefox/Code/icon-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 990 B |
11
Firefox/Code/icon.svg
Normal file
11
Firefox/Code/icon.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||
<!-- Background -->
|
||||
<rect width="48" height="48" rx="8" fill="#2196F3"/>
|
||||
|
||||
<!-- SQL text -->
|
||||
<text x="24" y="20" font-family="Arial, sans-serif" font-size="12" font-weight="bold" text-anchor="middle" fill="white">SQL</text>
|
||||
|
||||
<!-- Extract arrow -->
|
||||
<path d="M18 28 L24 34 L30 28" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 32 L24 38" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
42
Firefox/Code/manifest.json
Normal file
42
Firefox/Code/manifest.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "SimplyReports SQL Extractor",
|
||||
"version": "1.0",
|
||||
"description": "Grab the SQL query used in a SimplyReports results page and copy it to the clipboard or save it to a file.",
|
||||
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"clipboardWrite",
|
||||
"downloads"
|
||||
],
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"]
|
||||
}
|
||||
],
|
||||
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"persistent": false
|
||||
},
|
||||
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_title": "Extract SQL Query",
|
||||
"default_icon": {
|
||||
"16": "icon-16.png",
|
||||
"32": "icon-32.png",
|
||||
"48": "icon-48.png",
|
||||
"128": "icon-128.png"
|
||||
}
|
||||
},
|
||||
|
||||
"icons": {
|
||||
"16": "icon-16.png",
|
||||
"32": "icon-32.png",
|
||||
"48": "icon-48.png",
|
||||
"128": "icon-128.png"
|
||||
}
|
||||
}
|
||||
149
Firefox/Code/popup.html
Normal file
149
Firefox/Code/popup.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
width: 350px;
|
||||
padding: 15px;
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.header h3 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.sql-preview {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.filename-input {
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h3>SQL Query Extractor</h3>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status" style="display: none;"></div>
|
||||
|
||||
<div id="sqlPreview" class="sql-preview" style="display: none;"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="extractBtn" class="btn-primary">Extract SQL Query</button>
|
||||
|
||||
<div id="actionButtons" style="display: none;">
|
||||
<button id="copyBtn" class="btn-success">Copy to Clipboard</button>
|
||||
<input type="text" id="filenameInput" class="filename-input" placeholder="Enter filename (optional)">
|
||||
<button id="downloadBtn" class="btn-secondary">Save as File</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Click "Extract SQL Query" to search for SQL in the current page
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
167
Firefox/Code/popup.js
Normal file
167
Firefox/Code/popup.js
Normal file
@@ -0,0 +1,167 @@
|
||||
// popup.js - Popup script for user interactions
|
||||
|
||||
let currentSQLQuery = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const extractBtn = document.getElementById('extractBtn');
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
const filenameInput = document.getElementById('filenameInput');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const sqlPreview = document.getElementById('sqlPreview');
|
||||
const actionButtons = document.getElementById('actionButtons');
|
||||
|
||||
// Extract SQL Query
|
||||
extractBtn.addEventListener('click', function() {
|
||||
extractBtn.disabled = true;
|
||||
extractBtn.textContent = 'Extracting...';
|
||||
|
||||
// Send message to content script
|
||||
browser.tabs.query({ active: true, currentWindow: true }, function(tabs) {
|
||||
browser.tabs.sendMessage(tabs[0].id, { action: "extractSQL" }, function(response) {
|
||||
extractBtn.disabled = false;
|
||||
extractBtn.textContent = 'Extract SQL Query';
|
||||
|
||||
if (response && response.success) {
|
||||
// Debug: Log the original SQL
|
||||
console.log('Original SQL:', response.sqlQuery);
|
||||
|
||||
// Store the formatted version as the main query
|
||||
currentSQLQuery = formatSQL(response.sqlQuery);
|
||||
|
||||
// Debug: Log the formatted SQL
|
||||
console.log('Formatted SQL:', currentSQLQuery);
|
||||
console.log('Formatted SQL contains newlines:', currentSQLQuery.includes('\n'));
|
||||
|
||||
showStatus('success', 'SQL Query found and extracted!');
|
||||
showSQLPreview(currentSQLQuery);
|
||||
actionButtons.style.display = 'block';
|
||||
|
||||
// Set default filename based on current page
|
||||
const url = new URL(response.url);
|
||||
const defaultName = `sql_query_${url.hostname}_${new Date().toISOString().slice(0, 10)}.sql`;
|
||||
filenameInput.value = defaultName;
|
||||
} else {
|
||||
showStatus('error', 'No SQL query found in the current page');
|
||||
actionButtons.style.display = 'none';
|
||||
sqlPreview.style.display = 'none';
|
||||
currentSQLQuery = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Copy to Clipboard
|
||||
copyBtn.addEventListener('click', function() {
|
||||
if (!currentSQLQuery) return;
|
||||
|
||||
// Debug: Log what we're trying to copy
|
||||
console.log('Copying SQL:', currentSQLQuery);
|
||||
console.log('SQL length:', currentSQLQuery.length);
|
||||
console.log('Contains newlines:', currentSQLQuery.includes('\n'));
|
||||
|
||||
navigator.clipboard.writeText(currentSQLQuery).then(function() {
|
||||
const originalText = copyBtn.textContent;
|
||||
copyBtn.textContent = 'Copied!';
|
||||
copyBtn.style.backgroundColor = '#28a745';
|
||||
|
||||
setTimeout(function() {
|
||||
copyBtn.textContent = originalText;
|
||||
copyBtn.style.backgroundColor = '';
|
||||
}, 1500);
|
||||
}).catch(function(err) {
|
||||
showStatus('error', 'Failed to copy to clipboard: ' + err.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Download as File
|
||||
downloadBtn.addEventListener('click', function() {
|
||||
if (!currentSQLQuery) return;
|
||||
|
||||
downloadBtn.disabled = true;
|
||||
downloadBtn.textContent = 'Downloading...';
|
||||
|
||||
const filename = filenameInput.value.trim() || null;
|
||||
|
||||
// Send message to background script
|
||||
browser.runtime.sendMessage({
|
||||
action: "downloadSQL",
|
||||
sqlQuery: currentSQLQuery,
|
||||
filename: filename
|
||||
}, function(response) {
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.textContent = 'Save as File';
|
||||
|
||||
if (response && response.success) {
|
||||
const originalText = downloadBtn.textContent;
|
||||
downloadBtn.textContent = 'Downloaded!';
|
||||
downloadBtn.style.backgroundColor = '#28a745';
|
||||
|
||||
setTimeout(function() {
|
||||
downloadBtn.textContent = originalText;
|
||||
downloadBtn.style.backgroundColor = '';
|
||||
}, 1500);
|
||||
} else {
|
||||
showStatus('error', 'Failed to download file: ' + (response ? response.error : 'Unknown error'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function showStatus(type, message) {
|
||||
statusDiv.className = `status ${type}`;
|
||||
statusDiv.textContent = message;
|
||||
statusDiv.style.display = 'block';
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(function() {
|
||||
statusDiv.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function showSQLPreview(sqlQuery) {
|
||||
sqlPreview.textContent = sqlQuery;
|
||||
sqlPreview.style.display = 'block';
|
||||
}
|
||||
|
||||
function formatSQL(sql) {
|
||||
// Simple but reliable SQL formatting
|
||||
let formatted = sql.trim();
|
||||
|
||||
// Normalize whitespace first
|
||||
formatted = formatted.replace(/\s+/g, ' ');
|
||||
|
||||
// Add line breaks before major keywords (simple approach)
|
||||
formatted = formatted.replace(/\sFROM\s/gi, '\nFROM ');
|
||||
formatted = formatted.replace(/\sWHERE\s/gi, '\nWHERE ');
|
||||
formatted = formatted.replace(/\sAND\s/gi, '\n AND ');
|
||||
formatted = formatted.replace(/\sOR\s/gi, '\n OR ');
|
||||
formatted = formatted.replace(/\sORDER\sBY\s/gi, '\nORDER BY ');
|
||||
formatted = formatted.replace(/\sGROUP\sBY\s/gi, '\nGROUP BY ');
|
||||
formatted = formatted.replace(/\sHAVING\s/gi, '\nHAVING ');
|
||||
formatted = formatted.replace(/\sJOIN\s/gi, '\nJOIN ');
|
||||
formatted = formatted.replace(/\sINNER\sJOIN\s/gi, '\nINNER JOIN ');
|
||||
formatted = formatted.replace(/\sLEFT\sJOIN\s/gi, '\nLEFT JOIN ');
|
||||
formatted = formatted.replace(/\sRIGHT\sJOIN\s/gi, '\nRIGHT JOIN ');
|
||||
|
||||
// Add semicolon if missing
|
||||
if (!formatted.trim().endsWith(';')) {
|
||||
formatted += ';';
|
||||
}
|
||||
|
||||
// Clean up any leading/trailing whitespace on lines
|
||||
formatted = formatted.split('\n').map(line => line.trim()).join('\n');
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// Auto-extract on popup open if we're on a page that might have SQL
|
||||
browser.tabs.query({ active: true, currentWindow: true }, function(tabs) {
|
||||
const currentTab = tabs[0];
|
||||
if (currentTab.url && !currentTab.url.startsWith('chrome://') && !currentTab.url.startsWith('moz-extension://')) {
|
||||
// Auto-extract after a short delay
|
||||
setTimeout(function() {
|
||||
extractBtn.click();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user