Initial Kapa.ai chat integration.

Continue refactoring JavaScript into a component pattern and ESM.
Replaces some jQuery with native DOM API.

chore(ai): reference documentation and instructions for training AI

chore(ai): implement Kapa AI chat widget
- Move script tag to HTML template to make it obvious.
- Cleanup javascript to make it more component-like
- Set Kapa attributes, support setting userid

chore(js): add JS dependencies, previously referenced in script tags, to package.json for JS builds.

fix(api): indents

chore(js): package Mermaid diagram library

chore(js): refactor JS for AIChat and Theme as examples of using the component pattern for HTML/CSS/JS

chore(js): Use the new local-storage API in refactored module code and in code not yet ported. Cleanup syntax in local-storage and make functions available from window.LocalStorageAPI.

fix(js): theme.js name-change

chore(js): fix ai-chat.js file name

fix(js): refactor:
- componentNames are snakecase in HTML
- replace DOM selection method and jQuery eventhandler assignment
- remove old theme.js references

chore(ai): configure chat window overlay, size, and position:
- removes overlay and scroll lock
- positions chat to the right and bottom
- expands sample question width to 12 cols

chore(ai): edit disclaimer

fix(ai): size and position

chore(js): make ai-chat specific to configuration and and setting userid (for testing and future use).

fix(js): copy referrerHost variable to v3-wayfinding instead of relying on influxdb-url to assign it.

chore(ai): add a footer div at page bottom to contain modal triggers for custom-time and ask-ai. Still needs some CSS help. Moves tooltip text from CSS to HTML data attribute.

chore(ai): dynamically load AI script tag after DOMContentLoaded to avoid race conditions. Call initialization from the modal trigger module and pass the show trigger function to the onload handler.

fix(ai): fix modal triggers to viewport

fix(modal-triggers): stack the triggers into a single column.

restyle footer widgets

updated time selector modal to use correct storage term

minor style update

WIP(ai-chat): get product data

chore(js): Factor out pageContext module from influxdb-url.js

chore(js): Refactor helpers.js out of inflluxdb-url.js

WIP: refactor influxdburl - minimal changes for module conversions

feat(ai): Custom AI chat example questions product and version.
Ask AI example questions:
- Adds support for customizing example Ask AI questions per product or version.
- Configure questions in site `data/products.yml`; otherwise, it uses default questions from `ask-ai.js`

Context, page, and product data:
- Adds sample URLs for remaining versions in influxdb_urls
- `page-context.js` consolidates and exports constants for page context (protocol, host, path, referrer) and path-to-data mappings for product and influxdb_url site data

Module refactor:
- Refactors some JavaScript into ES6 modules, and refactors some of those further into a Component pattern--just vanilla JS and no shadow DOM stuff. The Component pattern that uses data attributes to "bind" JavaScript modules with CSS and HTML is a popular approach in modern web development. This pattern enhances modularity, reusability, and maintainability by associating behavior (JavaScript), structure (HTML), and style (CSS) through the use of data attributes.
- `assets/main.js` is the entrypoint
- Passes pageParams from the Hugo page to modules that import `@params`.
- Moves most external dependencies out of `script` tags and into package.json to be managed with `yarn`.
- Adds `eslint`.
- For modules that aren't yet components, wraps execution statements inside an `initialize()` function and calls the function from `main.js` on `DOMContentLoaded`.
- For components, if the page contains the `data-component="<component-name>"`, the matching element is passed to the component function on `DOMContentLoaded`.
- I tried to avoid changing logic where it wasn't necessary.

Update DOC_GPT_PROFILE.md

customize ai chat modal styles

fix(influxdb-url): Rename to cloud_dedicated in influxdb_urls.yml, remove newly added placeholder URL and use the extant default, refactor
- Rename  to  in influxdb_urls.yml
- Fix influxdb-url.js and data provision in local-storage.js to use the new name, mapping it to  to retain the existing local storage key

chore(api-lib): Use local-storage import instead of window global

chore(js): cleanup

fix(js): Ensure feature-callout initializes on page load

fix(theme): Load preferred theme before making the page visible. Execute a predefined function by specifying the function name in data-theme-callback

fix(search-toggle): Restores toggling the search field when sidebar is collapsed. Moves the event handler to a new search-button component

fix(ai): Fix custom attribute assignment. Rename property to ai_example_questions

Include the word `Bearer` or `Token`, a space, and your **token** value (all case-sensitive). Fix TOC links.
Fixes #5781

fix(api-docs): Update API reference directories and generation script for influxdb3 URL paths, update links and names in reference content

fix(api-ref): Update getswagger.sh destination paths to use the new directory structure when fetching spec files. Update the redocly  plugin module path.

hotfix: fix hlevel bug in children shortcode

Remove underline from custom time widget

add color to custom time widget styling
pull/5830/head
Jason Stirnaman 2024-12-20 17:14:19 -06:00
parent a15f8c8154
commit c173edce68
52 changed files with 3845 additions and 1423 deletions

52
DOC_GPT_PROFILE.md Normal file
View File

@ -0,0 +1,52 @@
Doc is a public custom GPT for OpenAI ChatGPT used to help write and style InfluxData and InfluxDB documentation.
## Introduction
Doc writes technical software documentation for InfluxData. The public web site is https://docs.influxdata.com and the source repository is https://github.com/influxdata/docs-v2.
Documentation provides step-by-step guides and reference documentation for InfluxDB and associated clients (CLIs, client libraries (SDKs), and Telegraf (https://docs.influxdata.com/telegraf/v1/)), and the legacy v1 components Kapacitor and Chronograf.
## Instruction
When a user asks a question and doesn't include a product from the list below, ask them which product in the list they are using, along with the version and query language:
InfluxDB OSS 1.x (v1)
- Documentation: https://docs.influxdata.com/influxdb/v1/
- Query languages: v1.8+ supports InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1 client libraries
InfluxDB Enterprise (v1)
- Documentation: https://docs.influxdata.com/enterprise_influxdb/v1/
- Query languages: v1.8+ supports InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1 client libraries
InfluxDB OSS 2.x (v2)
- Documentation: https://docs.influxdata.com/influxdb/v2/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
InfluxDB Cloud (v2, multi-tenant)
- Documentation: https://docs.influxdata.com/influxdb/cloud/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
InfluxDB Clustered (v3, 3.0, self-managed distributed)
- Documentation: https://docs.influxdata.com/influxdb/clustered/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Cloud Dedicated (3.0, v3, InfluxData-managed single tenant)
- Documentation: https://docs.influxdata.com/influxdb/cloud-dedicated/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Cloud Serverless (v3, 3.0, InfluxData-managed multi-tenant)
- Documentation: https://docs.influxdata.com/influxdb/clustered/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influx CLI, v3 client libraries
If I ask about a REST API or SDK (client library) and don't specify a product, ask which product.
For API client libraries, refer to the documentation and to the source repositories in https://github.com/InfluxCommunity for the version-specific client library.
When writing documentation, always use Google Developer Documentation style guidelines and Markdown format.
If writing REST API reference documentation follow YouTube Data API style and Google Developer Documentation style guidelines.
The project uses the Hugo static site generator to build the documentation.
The site uses JavaScript and jQuery.
For information about linting, tests (using pytests for codeblocks), shortcode <shortcode_name>, refer to https://github.com/influxdata/docs-v2/blob/master/README.md and https://github.com/influxdata/docs-v2/blob/master/CONTRIBUTING.md.
If something in CONTRIBUTING.md needs clarification, then give me the suggested revision for CONTRIBUTING.md in Markdown.
The community forum is https://community.influxdata.com/ and should not be used as a primary source of information, but might contain useful suggestions or solutions to specific problems from users.

30
PLATFORM_REFERENCE.md Normal file
View File

@ -0,0 +1,30 @@
When a user asks a question and doesn't include a product from the list below, ask them which product in the list they are using, along with the version and query language:
InfluxDB OSS 1.x (v1)
- Documentation: https://docs.influxdata.com/influxdb/v1/
- Query languages: v1.8+ supports InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1 client libraries
InfluxDB Enterprise (v1)
- Documentation: https://docs.influxdata.com/enterprise_influxdb/v1/
- Query languages: v1.8+ supports InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1 client libraries
InfluxDB OSS 2.x (v2)
- Documentation: https://docs.influxdata.com/influxdb/v2/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
InfluxDB Cloud (v2, multi-tenant)
- Documentation: https://docs.influxdata.com/influxdb/cloud/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
InfluxDB Clustered (v3, 3.0, self-managed distributed)
- Documentation: https://docs.influxdata.com/influxdb/clustered/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Cloud Dedicated (3.0, v3, InfluxData-managed single tenant)
- Documentation: https://docs.influxdata.com/influxdb/cloud-dedicated/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Cloud Serverless (v3, 3.0, InfluxData-managed multi-tenant)
- Documentation: https://docs.influxdata.com/influxdb/clustered/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influx CLI, v3 client libraries

View File

@ -1,37 +1,43 @@
////////////////////////////////////////////////////////////////////////////////
///////////////// Preferred Client Library programming language ///////////////
////////////////////////////////////////////////////////////////////////////////
import { activateTabs, updateBtnURLs } from './tabbed-content.js';
import { getPreference, setPreference } from './local-storage.js';
function getVisitedApiLib () {
function getVisitedApiLib() {
const path = window.location.pathname.match(
/client-libraries\/((?:v[0-9]|flight)\/)?([a-zA-Z0-9]*)/
);
return path && path.length && path[2];
}
function isApiLib () {
function isApiLib() {
return /\/client-libraries\//.test(window.location.pathname);
}
// Set the user's programming language (client library) preference.
function setApiLibPreference (preference) {
function setApiLibPreference(preference) {
setPreference('api_lib', preference);
}
// Retrieve the user's programming language (client library) preference.
function getApiLibPreference () {
function getApiLibPreference() {
return getPreference('api_lib') || '';
}
// When visit a client library page, set the api_lib preference
if (isApiLib()) {
function initialize() {
// When visiting a client library page, set the api_lib preference
if (isApiLib()) {
var selectedApiLib = getVisitedApiLib();
setPreference('api_lib', selectedApiLib);
}
// Activate code-tabs based on the cookie then override with query param.
const tab = getApiLibPreference();
['.tabs, .code-tabs'].forEach(
(selector) => activateTabs(selector, tab),
updateBtnURLs(tab)
);
}
// Activate code-tabs based on the cookie then override with query param.
var tab = getApiLibPreference();
['.tabs, .code-tabs'].forEach(
selector => activateTabs(selector, tab),
updateBtnURLs(tab)
);
export { initialize };

View File

@ -0,0 +1,19 @@
import AskAI from './ask-ai.js';
function showTrigger(element) {
// Remove the inline display: none style
element.removeAttribute('style');
}
export default function AskAITrigger({ component }) {
const kapaContainer = document.querySelector('#kapa-widget-container');
if (!component && !kapaContainer) {
return;
}
if (!kapaContainer) {
// Initialize the chat widget
AskAI({ onChatLoad: () => showTrigger(component) });
} else {
showTrigger(component);
}
}

100
assets/js/ask-ai.js Normal file
View File

@ -0,0 +1,100 @@
import { productData } from './page-context.js';
function setUser(userid, email) {
const NAMESPACE = 'kapaSettings';
// Set the user ID and email in the global settings namespace.
// The chat widget will use this on subsequent chats to personalize the user's experience.
window[NAMESPACE] = {
user: {
uniqueClientId: userid,
email: email,
}
}
}
// Initialize the chat widget
function initializeChat({onChatLoad, chatAttributes}) {
/* See https://docs.kapa.ai/integrations/website-widget/configuration for
* available configuration options.
* All values are strings.
*/
const requiredAttributes = {
websiteId: 'a02bca75-1dd3-411e-95c0-79ee1139be4d',
projectName: 'InfluxDB',
projectColor: '#020a47',
projectLogo: '/img/influx-logo-cubo-white.png',
}
const optionalAttributes = {
modalDisclaimer: 'This AI can access [documentation for InfluxDB, clients, and related tools](https://docs.influxdata.com). Information you submit is used in accordance with our [Privacy Policy](https://www.influxdata.com/legal/privacy-policy/).',
modalExampleQuestions: 'Use Python to write data to InfluxDB 3,How do I query using SQL?,How do I use MQTT with Telegraf?',
buttonHide: 'true',
modalOpenOnCommandK: 'true',
modalExampleQuestionsColSpan: '8',
modalFullScreenOnMobile: 'true',
modalHeaderPadding: '.5rem',
modalInnerPositionRight: '0',
modalInnerPositionLeft: '',
modalLockScroll: 'false',
modalOverrideOpenClassAskAi: 'ask-ai-open',
modalSize: '500px',
modalWithOverlay: 'false',
modalInnerMaxWidth: '500px',
modalXOffset: '1rem',
modalYOffset: '10vh',
userAnalyticsFingerprintEnabled: 'true',
fontFamily: 'Proxima Nova, sans-serif',
modalHeaderBgColor: 'linear-gradient(90deg, #d30971 0%, #9b2aff 100%)',
modalHeaderBorderBottom: 'none',
modalTitleColor: '#fff',
modalTitleFontSize: '1.25rem',
}
const scriptUrl = 'https://widget.kapa.ai/kapa-widget.bundle.js';
const script = document.createElement('script');
script.async = true;
script.src = scriptUrl;
script.onload = function() {
onChatLoad();
window.influxdatadocs.AskAI = AskAI;
};
script.onerror = function() {
console.error('Error loading AI chat widget script');
};
const dataset = {...requiredAttributes, ...optionalAttributes, ...chatAttributes};
Object.keys(dataset).forEach(key => {
// Assign dataset attributes from the object
script.dataset[key] = dataset[key];
});
// Check for an existing script element to remove
const oldScript= document.querySelector(`script[src="${scriptUrl}"]`);
if (oldScript) {
oldScript.remove();
}
document.head.appendChild(script);
}
function getProductExampleQuestions() {
const questions = productData?.product?.ai_sample_questions || null;
return questions?.join(',') || '';
}
/**
* chatParams: specify custom (for example, page-specific) attribute values for the chat, pass the dataset key-values (collected in ...chatParams). See https://docs.kapa.ai/integrations/website-widget/configuration for available configuration options.
* onChatLoad: function to call when the chat widget has loaded
* userid: optional, a unique user ID for the user (not currently used for public docs)
*/
export default function AskAI({ userid, email, onChatLoad, ...chatParams }) {
const chatAttributes = {
modalExampleQuestions: getProductExampleQuestions(),
...chatParams,
}
initializeChat({onChatLoad, chatAttributes});
if (userid) {
setUser(userid, email);
}
}

View File

@ -1,7 +1,10 @@
var codeBlockSelector = ".article--content pre";
var codeBlocks = $(codeBlockSelector);
import $ from 'jquery';
var appendHTML = `
function initialize() {
var codeBlockSelector = '.article--content pre';
var codeBlocks = $(codeBlockSelector);
var appendHTML = `
<div class="code-controls">
<span class="code-controls-toggle"><span class='cf-icon More'></span></span>
<ul class="code-control-options">
@ -9,98 +12,106 @@ var appendHTML = `
<li class='fullscreen-toggle'><span class='cf-icon ExpandB'></span> Fill window</li>
</ul>
</div>
`
`;
// Wrap all codeblocks with a new 'codeblock' div
$(codeBlocks).each(function() {
// Wrap all codeblocks with a new 'codeblock' div
$(codeBlocks).each(function () {
$(this).wrap("<div class='codeblock'></div>");
});
});
// Append code controls to all codeblock divs
$('.codeblock').append(appendHTML);
// Append code controls to all codeblock divs
$('.codeblock').append(appendHTML);
//////////////////////////// CODE CONTROLS TOGGLING ////////////////////////////
//////////////////////////// CODE CONTROLS TOGGLING ////////////////////////////
// Click outside of the code-controls to close them
$(document).click(function() {
$('.code-controls').removeClass('open')
});
// Click outside of the code-controls to close them
$(document).click(function () {
$('.code-controls').removeClass('open');
});
// Click the code controls toggle to open code controls
$('.code-controls-toggle').click(function() {
// Click the code controls toggle to open code controls
$('.code-controls-toggle').click(function () {
$(this).parent('.code-controls').toggleClass('open');
})
});
// Stop event propagation for clicks inside of the code-controls div
$('.code-controls').click(function(e) {
// Stop event propagation for clicks inside of the code-controls div
$('.code-controls').click(function (e) {
e.stopPropagation();
});
});
/////////////////////////////// COPY TO CLIPBOARD //////////////////////////////
/////////////////////////////// COPY TO CLIPBOARD //////////////////////////////
// Update button text during lifecycles
function updateText(element, currentText, newText) {
let inner = (element)[0].innerHTML;
inner = inner.replace(currentText, newText)
// Update button text during lifecycles
function updateText(element, currentText, newText) {
let inner = element[0].innerHTML;
inner = inner.replace(currentText, newText);
element[0].innerHTML = inner
}
element[0].innerHTML = inner;
}
// Trigger copy success state lifecycle
function copyLifeCycle(element, state) {
let stateData = ((state === 'success') ? {state: 'success', message: 'Copied!'} : {state: 'failed', message: 'Copy failed!'})
// Trigger copy success state lifecycle
function copyLifeCycle(element, state) {
let stateData =
state === 'success'
? { state: 'success', message: 'Copied!' }
: { state: 'failed', message: 'Copy failed!' };
updateText(element, 'Copy', stateData.message)
element.addClass(stateData.state)
updateText(element, 'Copy', stateData.message);
element.addClass(stateData.state);
setTimeout(function() {
setTimeout(function () {
updateText(element, stateData.message, 'Copy');
element.removeClass(stateData.state)
}, 2500)
}
element.removeClass(stateData.state);
}, 2500);
}
// Trigger copy failure state lifecycle
// Trigger copy failure state lifecycle
$('.copy-code').click(function() {
let text = $(this).closest('.code-controls').prev('pre')[0].innerText
$('.copy-code').click(function () {
let text = $(this).closest('.code-controls').prev('pre')[0].innerText;
const copyContent = async () => {
try {
await navigator.clipboard.writeText(text);
copyLifeCycle($(this), 'success')
} catch (err) {
copyLifeCycle($(this), 'failed')
}
copyLifeCycle($(this), 'success');
} catch {
copyLifeCycle($(this), 'failed');
}
};
copyContent()
})
copyContent();
});
/////////////////////////////// FULL WINDOW CODE ///////////////////////////////
/////////////////////////////// FULL WINDOW CODE ///////////////////////////////
/*
/*
On click, open the fullscreen code modal and append a clone of the selected codeblock.
Disable scrolling on the body.
Disable user selection on everything but the fullscreen codeblock.
*/
$('.fullscreen-toggle').click(function() {
$('.fullscreen-toggle').click(function () {
var code = $(this).closest('.code-controls').prev('pre').clone();
$('#fullscreen-code-placeholder').replaceWith(code[0]);
$('body').css('overflow', 'hidden');
$('body > div:not(.fullscreen-code)').css('user-select', 'none');
$('.fullscreen-code').fadeIn();
})
});
/*
/*
On click, close the fullscreen code block.
Reenable scrolling on the body.
Reenable user selection on everything.
Close the modal and replace the code block with the placeholder element.
*/
$('.fullscreen-close').click(function() {
$('.fullscreen-close').click(function () {
$('body').css('overflow', 'auto');
$('body > div:not(.fullscreen-code)').css('user-select', '');
$('.fullscreen-code').fadeOut();
$('.fullscreen-code pre').replaceWith('<div id="fullscreen-code-placeholder"></div>')
});
$('.fullscreen-code pre').replaceWith(
'<div id="fullscreen-code-placeholder"></div>'
);
});
}
export { initialize };

View File

@ -1,154 +1,200 @@
import $ from 'jquery';
///////////////////////////// Make headers linkable /////////////////////////////
var headingWhiteList = $("\
function makeHeadersLinkable() {
var headingWhiteList = $(
'\
.article--content h2, \
.article--content h3, \
.article--content h4, \
.article--content h5, \
.article--content h6 \
");
'
);
var headingBlackList = ("\
var headingBlackList =
'\
.influxdbu-banner h4 \
");
';
headingElements = headingWhiteList.not(headingBlackList);
const headingElements = headingWhiteList.not(headingBlackList);
headingElements.each(function() {
headingElements.each(function () {
function getLink(element) {
return ((element.attr('href') === undefined ) ? $(element).attr("id") : element.attr('href'))
return element.attr('href') === undefined
? $(element).attr('id')
: element.attr('href');
}
var link = "<a href=\"#" + getLink($(this)) + "\"></a>"
$(this).wrapInner( link );
})
var link = '<a href="#' + getLink($(this)) + '"></a>';
$(this).wrapInner(link);
});
}
///////////////////////////////// Smooth Scroll /////////////////////////////////
var elementWhiteList = [
".tabs p a",
".code-tabs p a",
".children-links a",
".list-links a",
"a.url-trigger",
"a.fullscreen-close"
]
function smoothScroll() {
var elementWhiteList = [
'.tabs p a',
'.code-tabs p a',
'.children-links a',
'.list-links a',
'a.url-trigger',
'a.fullscreen-close',
];
$('.article a[href^="#"]:not(' + elementWhiteList + ')').click(function (e) {
e.preventDefault();
scrollToAnchor(this.hash);
});
}
function scrollToAnchor(target) {
var $target = $(target);
if($target && $target.length > 0) {
$('html, body').stop().animate({
'scrollTop': ($target.offset().top)
}, 400, 'swing', function () {
if ($target && $target.length > 0) {
$('html, body')
.stop()
.animate(
{
scrollTop: $target.offset().top,
},
400,
'swing',
function () {
window.location.hash = target;
});
}
);
// Unique accordion functionality
// If the target is an accordion element, open the accordion after scrolling
if ($target.hasClass('expand')) {
if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {}
else {
if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {
// Do nothing?
} else {
$(target + '> .expand-label').trigger('click');
};
};
}
}
}
}
$('.article a[href^="#"]:not(' + elementWhiteList + ')').click(function (e) {
e.preventDefault();
scrollToAnchor(this.hash);
});
///////////////////////////// Left Nav Interactions /////////////////////////////
$(".children-toggle").click(function(e) {
e.preventDefault()
function leftNavInteractions() {
$('.children-toggle').click(function (e) {
e.preventDefault();
$(this).toggleClass('open');
$(this).siblings('.children').toggleClass('open');
})
});
}
//////////////////////////// Mobile Contents Toggle ////////////////////////////
$('#contents-toggle-btn').click(function(e) {
function mobileContentsToggle() {
$('#contents-toggle-btn').click(function (e) {
e.preventDefault();
$(this).toggleClass('open');
$('#nav-tree').toggleClass('open');
})
});
}
/////////////////////////////// Truncate Content ///////////////////////////////
$(".truncate-toggle").click(function(e) {
e.preventDefault()
var truncateParent = $(this).closest('.truncate')
var truncateParentID = $(this).closest('.truncate')[0].id
function truncateContent() {
$('.truncate-toggle').click(function (e) {
e.preventDefault();
var truncateParent = $(this).closest('.truncate');
var truncateParentID = $(this).closest('.truncate')[0].id;
if (truncateParent.hasClass('closed')) {
$(this)[0].href = `#${truncateParentID}`
$(this)[0].href = `#${truncateParentID}`;
} else {
$(this)[0].href = "#"
$(this)[0].href = '#';
}
truncateParent.toggleClass('closed')
truncateParent.find('.truncate-content').toggleClass('closed')
})
truncateParent.toggleClass('closed');
truncateParent.find('.truncate-content').toggleClass('closed');
});
}
////////////////////////////// Expand Accordions ///////////////////////////////
function expandAccordions() {
$('.expand-label').click(function () {
$(this).children('.expand-toggle').toggleClass('open');
$(this).next('.expand-content').slideToggle(200);
});
$('.expand-label').click(function() {
$(this).children('.expand-toggle').toggleClass('open')
$(this).next('.expand-content').slideToggle(200)
})
// Expand accordions on load based on URL anchor
function openAccordionByHash() {
// Expand accordions on load based on URL anchor
function openAccordionByHash() {
var anchor = window.location.hash;
function expandElement() {
if ($(anchor).parents('.expand').length > 0) {
return $(anchor).closest('.expand').children('.expand-label');
} else if ($(anchor).hasClass('expand')){
} else if ($(anchor).hasClass('expand')) {
return $(anchor).children('.expand-label');
}
};
}
if (expandElement() != null) {
if (expandElement().children('.expand-toggle').hasClass('open')) {}
else {
if (expandElement().children('.expand-toggle').hasClass('open')) {
// Do nothing?
} else {
expandElement().children('.expand-toggle').trigger('click');
};
};
};
// Open accordions by hash on page load.
openAccordionByHash()
}
}
}
// Open accordions by hash on page load.
openAccordionByHash();
}
////////////////////////// Inject tooltips on load //////////////////////////////
$('.tooltip').each( function(){
$toolTipText = $('<div/>').addClass('tooltip-text').text($(this).attr('data-tooltip-text'));
$toolTipElement = $('<div/>').addClass('tooltip-container').append($toolTipText);
function injectTooltips() {
$('.tooltip').each(function () {
const $toolTipText = $('<div/>')
.addClass('tooltip-text')
.text($(this).attr('data-tooltip-text'));
const $toolTipElement = $('<div/>')
.addClass('tooltip-container')
.append($toolTipText);
$(this).prepend($toolTipElement);
});
});
}
//////////////////// Style time cells in tables to not wrap ////////////////////
$('.article--content table').each(function() {
function styleTimeCells() {
$('.article--content table').each(function () {
var table = $(this);
table.find('td').each(function() {
let cellContent = $(this)[0].innerText
table.find('td').each(function () {
let cellContent = $(this)[0].innerText;
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*Z/.test(cellContent)) {
$(this).addClass('nowrap')
$(this).addClass('nowrap');
}
})
})
});
});
}
/////////////////////// Open external links in a new tab ///////////////////////
$('.article--content a').each(function() {
function openExternalLinks() {
$('.article--content a').each(function () {
var currentHost = location.host;
if (!($(this)[0].href).includes(currentHost)) {
if (!$(this)[0].href.includes(currentHost)) {
$(this).attr('target', '_blank');
};
})
}
});
}
/////////////////////// Initialize all functions //////////////////////////////
function initialize() {
makeHeadersLinkable();
smoothScroll();
leftNavInteractions();
mobileContentsToggle();
truncateContent();
expandAccordions();
injectTooltips();
styleTimeCells();
openExternalLinks();
}
export { initialize, scrollToAnchor };

View File

@ -1,13 +1,18 @@
import $ from 'jquery';
import { Datepicker } from 'vanillajs-datepicker';
import { toggleModal } from './modals.js';
import * as localStorage from './local-storage.js';
// Placeholder start date used in InfluxDB custom timestamps
const defaultStartDate = '2022-01-01';
// Return yyyy-mm-dd formatted string from a Date object
function formatDate (dateObj) {
function formatDate(dateObj) {
return dateObj.toISOString().replace(/T.*$/, '');
}
// Return yesterday's date
function yesterday () {
function yesterday() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
@ -15,25 +20,25 @@ function yesterday () {
}
// Split a date string into year, month, and day
function datePart (date) {
datePartRegex = /(\d{4})-(\d{2})-(\d{2})/;
year = date.replace(datePartRegex, '$1');
month = date.replace(datePartRegex, '$2');
day = date.replace(datePartRegex, '$3');
function datePart(date) {
const datePartRegex = /(\d{4})-(\d{2})-(\d{2})/;
const year = date.replace(datePartRegex, '$1');
const month = date.replace(datePartRegex, '$2');
const day = date.replace(datePartRegex, '$3');
return { year: year, month: month, day: day };
}
///////////////////////// PREFERENCE COOKIE MANAGEMENT /////////////////////////
prefID = 'sample_get_started_date';
const prefID = 'sample_get_started_date';
function setStartDate (setDate) {
setPreference(prefID, setDate);
function setStartDate(setDate) {
localStorage.setPreference(prefID, setDate);
}
function getStartDate () {
return getPreference(prefID);
function getStartDate() {
return localStorage.getPreference(prefID);
}
////////////////////////////////////////////////////////////////////////////////
@ -42,14 +47,14 @@ function getStartDate () {
var startDate = getStartDate() || yesterday();
// Convert a time value to a Unix timestamp (seconds)
function timeToUnixSeconds (time) {
unixSeconds = new Date(time).getTime() / 1000;
function timeToUnixSeconds(time) {
const unixSeconds = new Date(time).getTime() / 1000;
return unixSeconds;
}
// Default time values in getting started sample data
let times = [
// Default time values in getting started sample data
const defaultTimes = [
{
rfc3339: `${defaultStartDate}T08:00:00Z`,
unix: timeToUnixSeconds(`${defaultStartDate}T08:00:00Z`),
@ -102,11 +107,11 @@ let times = [
rfc3339: `${defaultStartDate}T20:00:00Z`,
unix: timeToUnixSeconds(`${defaultStartDate}T20:00:00Z`),
}, // 1641067200
];
];
function updateTimestamps (newStartDate) {
function updateTimestamps (newStartDate, seedTimes=defaultTimes) {
// Update the times array with replacement times
times = times.map(x => {
const times = seedTimes.map(x => {
var newStartTimestamp = x.rfc3339.replace(/^.*T/, newStartDate + 'T');
return {
@ -128,13 +133,13 @@ function updateTimestamps (newStartDate) {
var wrapper = $(this)[0];
times.forEach(function (x) {
oldDatePart = datePart(x.rfc3339.replace(/T.*$/, ''));
newDatePart = datePart(x.rfc3339_new.replace(/T.*$/, ''));
rfc3339Regex = new RegExp(
const oldDatePart = datePart(x.rfc3339.replace(/T.*$/, ''));
const newDatePart = datePart(x.rfc3339_new.replace(/T.*$/, ''));
const rfc3339Regex = new RegExp(
`${oldDatePart.year}(.*?)${oldDatePart.month}(.*?)${oldDatePart.day}`,
'g'
);
rfc3339Repl = `${newDatePart.year}$1${newDatePart.month}$2${newDatePart.day}`;
const rfc3339Repl = `${newDatePart.year}$1${newDatePart.month}$2${newDatePart.day}`;
wrapper.innerHTML = wrapper.innerHTML
.replaceAll(x.unix, x.unix_new)
@ -146,13 +151,13 @@ function updateTimestamps (newStartDate) {
var wrapper = $(this)[0];
times.forEach(function (x) {
oldDatePart = datePart(x.rfc3339.replace(/T.*$/, ''));
newDatePart = datePart(x.rfc3339_new.replace(/T.*$/, ''));
rfc3339Regex = new RegExp(
const oldDatePart = datePart(x.rfc3339.replace(/T.*$/, ''));
const newDatePart = datePart(x.rfc3339_new.replace(/T.*$/, ''));
const rfc3339Regex = new RegExp(
`${oldDatePart.year}-${oldDatePart.month}-${oldDatePart.day}`,
'g'
);
rfc3339Repl = `${newDatePart.year}-${newDatePart.month}-${newDatePart.day}`;
const rfc3339Repl = `${newDatePart.year}-${newDatePart.month}-${newDatePart.day}`;
wrapper.innerHTML = wrapper.innerHTML
.replaceAll(x.unix, x.unix_new)
@ -161,7 +166,7 @@ function updateTimestamps (newStartDate) {
});
// Create a new seed times array with new start time for next change
times = times.map(x => {
return times.map((x) => {
var newStartTimestamp = x.rfc3339.replace(/^.*T/, newStartDate + 'T');
return {
@ -173,38 +178,50 @@ function updateTimestamps (newStartDate) {
/////////////////////// MODAL INTERACTIONS / DATE PICKER ///////////////////////
// Date picker form element
var datePickerEl = $('#custom-date-selector');
function CustomTimeTrigger({component}) {
const $component = $(component);
$component
.find('a[data-action="open"]:first')
.on('click', () => toggleModal('#influxdb-gs-date-select'));
// Initialize the date picker with the current startDate
const elem = datePickerEl[0];
const datepicker = new Datepicker(elem, {
// Date picker form element
var datePickerEl = $('#custom-date-selector');
// Initialize the date picker with the current startDate
const elem = datePickerEl[0];
const datepicker = new Datepicker(elem, {
defaultViewDate: startDate,
format: 'yyyy-mm-dd',
nextArrow: '>',
prevArrow: '<',
});
});
//////////////////////////////////// ACTIONS ///////////////////////////////////
//////////////////////////////////// ACTIONS ///////////////////////////////////
// Initial update to yesterdays date ON PAGE LOAD
// Conditionally set the start date cookie it startDate is equal to the default value
updateTimestamps(startDate);
if (startDate === yesterday()) {
// Initial update to yesterdays date ON PAGE LOAD
// Conditionally set the start date cookie it startDate is equal to the default value
let updatedTimes = updateTimestamps(startDate, defaultTimes);
if (startDate === yesterday()) {
setStartDate(startDate);
}
}
// Sumbit new date
$('#submit-custom-date').click(function () {
// Submit new date
$('#submit-custom-date').click(function () {
let newDate = datepicker.getDate();
if (newDate != undefined) {
newDate = formatDate(newDate);
updateTimestamps(newDate);
// Update the last updated timestamps with the new date
// and reassign the updated times.
updatedTimes = updateTimestamps(newDate, updatedTimes);
setStartDate(newDate);
toggleModal('#influxdb-gs-date-select');
} else {
toggleModal('#influxdb-gs-date-select');
}
});
});
}
export { CustomTimeTrigger };

View File

@ -1,42 +0,0 @@
/*
Copied and pasted this script for CSS swaps w/ cookies from
http://www.thesitewizard.com/javascripts/change-style-sheets.shtml
*/
// *** TO BE CUSTOMISED ***
var style_preference_name = 'theme';
var style_cookie_duration = 30;
var style_domain = 'docs.influxdata.com';
// *** END OF CUSTOMISABLE SECTION ***
// You do not need to customise anything below this line
function switchStyle (css_title) {
// You may use this script on your site free of charge provided
// you do not remove this notice or the URL below. Script from
// http://www.thesitewizard.com/javascripts/change-style-sheets.shtml
var i, link_tag;
for (
i = 0, link_tag = document.getElementsByTagName('link');
i < link_tag.length;
i++
) {
if (
link_tag[i].rel.indexOf('stylesheet') != -1 &&
link_tag[i].title.includes('theme')
) {
link_tag[i].disabled = true;
if (link_tag[i].title == css_title) {
link_tag[i].disabled = false;
}
}
setPreference(style_preference_name, css_title.replace(/-theme/, ''));
}
}
function setStyleFromCookie () {
var css_title = `${getPreference(style_preference_name)}-theme`;
if (css_title !== undefined) {
switchStyle(css_title);
}
}

View File

@ -13,21 +13,23 @@ function getCalloutID (el) {
// Hide a callout and update the cookie with the viewed callout
function hideCallout (calloutID) {
if (!notificationIsRead(calloutID)) {
setNotificationAsRead(calloutID, 'callout');
if (!window.LocalStorageAPI.notificationIsRead(calloutID)) {
window.LocalStorageAPI.setNotificationAsRead(calloutID, 'callout');
$(`#${calloutID}`).fadeOut(200);
}
}
// Show the url feature callouts on page load
$('.feature-callout').each(function () {
calloutID = calloutID($(this));
$(document).ready(function () {
$('.feature-callout').each(function () {
const calloutID = getCalloutID($(this));
if (!notificationIsRead(calloutID, 'callout')) {
if (!window.LocalStorageAPI.notificationIsRead(calloutID, 'callout')) {
$(`#${calloutID}.feature-callout`)
.fadeIn(300)
.removeClass('start-position');
}
});
});
// Hide the InfluxDB URL selector callout

13
assets/js/helpers.js Normal file
View File

@ -0,0 +1,13 @@
/** Delay execution of a function `fn` for a number of milliseconds `ms`
* e.g., delay a validation handler to avoid annoying the user.
*/
function delay(fn, ms) {
let timer = 0;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), ms || 0);
};
}
export { delay };

View File

@ -1,58 +1,44 @@
var placeholderUrls = {
oss: 'http://localhost:8086',
cloud: 'https://cloud2.influxdata.com',
core: 'http://localhost:8181',
enterprise: 'http://localhost:8181',
serverless: 'https://cloud2.influxdata.com',
dedicated: 'cluster-id.a.influxdb.io',
clustered: 'cluster-host.com',
};
/*
NOTE: The defaultUrls variable is defined in assets/js/local-storage.js
////////////////////////////////////////////////////////////////////////////////
///////////////////////// INFLUXDB URL PREFERENCE /////////////////////////////
////////////////////////////////////////////////////////////////////////////////
*/
import * as pageParams from '@params';
import {
DEFAULT_STORAGE_URLS,
getPreference,
setPreference,
setInfluxDBUrls,
removeInfluxDBUrl,
getInfluxDBUrl,
getInfluxDBUrls,
} from './local-storage.js';
import $ from 'jquery';
import { context as PRODUCT_CONTEXT, referrerHost } from './page-context.js';
import { delay } from './helpers.js';
import { toggleModal } from './modals.js';
var elementSelector = '.article--content pre:not(.preserve)';
export const CLOUD_URLS = Object.values(pageParams.influxdb_urls.cloud.providers).flatMap((provider) => provider.regions?.map((region) => region.url));
// Return the page context (cloud, serverless, oss/enterprise, dedicated, clustered, other)
function context() {
if (/\/influxdb\/cloud\//.test(window.location.pathname)) {
return 'cloud';
} else if (/\/influxdb3\/core/.test(window.location.pathname)) {
return 'core';
} else if (/\/influxdb3\/enterprise/.test(window.location.pathname)) {
return 'enterprise';
} else if (/\/influxdb3\/cloud-serverless/.test(window.location.pathname)) {
return 'serverless';
} else if (/\/influxdb3\/cloud-dedicated/.test(window.location.pathname)) {
return 'dedicated';
} else if (/\/influxdb3\/clustered/.test(window.location.pathname)) {
return 'clustered';
} else if (
/\/(enterprise_|influxdb).*\/v[1-2]\//.test(window.location.pathname)
) {
return 'oss/enterprise';
} else {
return 'other';
}
}
export function InfluxDBUrl() {
const UNIQUE_URL_PRODUCTS = ['dedicated', 'clustered'];
const IS_UNIQUE_URL_PRODUCT = UNIQUE_URL_PRODUCTS.includes(PRODUCT_CONTEXT);
////////////////////////////////////////////////////////////////////////////////
///////////////////////// Session-management functions /////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Add actual cloud URLs as needed
const elementSelector = '.article--content pre:not(.preserve)';
// Retrieve the user's InfluxDB preference (cloud or oss) from the influxdb_pref
// local storage key. Default is cloud.
function getURLPreference() {
///////////////////// Stored preference management ///////////////////////
// Retrieve the user's InfluxDB preference (cloud or oss) from the influxdb_pref local storage key. Default is cloud.
function getURLPreference() {
return getPreference('influxdb_url');
}
}
// Set the user's selected InfluxDB preference (cloud or oss)
function setURLPreference(preference) {
// Set the user's selected InfluxDB preference (cloud or oss)
function setURLPreference(preference) {
setPreference('influxdb_url', preference);
}
}
/*
/*
influxdata_docs_urls local storage object keys:
- oss
@ -70,51 +56,51 @@ function setURLPreference(preference) {
- custom
*/
// Store URLs in the urls local storage object
function storeUrl(context, newUrl, prevUrl) {
urlsObj = {};
// Store URLs in the urls local storage object
function storeUrl(context, newUrl, prevUrl) {
let urlsObj = {};
urlsObj['prev_' + context] = prevUrl;
urlsObj[context] = newUrl;
setInfluxDBUrls(urlsObj);
}
}
// Store custom URL in the url local storage object
// Used to populate the custom URL field
function storeCustomUrl(customUrl) {
// Store custom URL in the url local storage object
// Used to populate the custom URL field
function storeCustomUrl(customUrl) {
setInfluxDBUrls({ custom: customUrl });
$('input#custom[type=radio]').val(customUrl);
}
}
// Set a URL in the urls local storage object to an empty string
// Used to clear the form when custom url input is left empty
function removeCustomUrl() {
// Set a URL in the urls local storage object to an empty string
// Used to clear the form when custom url input is left empty
function removeCustomUrl() {
removeInfluxDBUrl('custom');
}
}
// Store a product URL in the urls local storage object
// Used to populate the custom URL field
function storeProductUrl(product, productUrl) {
urlsObj = {};
// Store a product URL in the urls local storage object
// Used to populate the custom URL field
function storeProductUrl(product, productUrl) {
let urlsObj = {};
urlsObj[product] = productUrl;
setInfluxDBUrls(urlsObj);
$(`input#${product}-url-field`).val(productUrl);
}
}
// Set a product URL in the urls local storage object to an empty string
// Used to clear the form when dedicated url input is left empty
function removeProductUrl(product) {
// Set a product URL in the urls local storage object to an empty string
// Used to clear the form when dedicated url input is left empty
function removeProductUrl(product) {
removeInfluxDBUrl(product);
}
}
////////////////////////////////////////////////////////////////////////////////
//////////////////////// InfluxDB URL utility functions ////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//////////////////////// InfluxDB URL utility functions ////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Preserve URLs in codeblocks that come just after or are inside a div
// with the class, .keep-url
function addPreserve() {
// Preserve URLs in codeblocks that come just after or are inside a div
// with the class, .keep-url
function addPreserve() {
$('.keep-url').each(function () {
// For code blocks with no syntax highlighting
$(this).next('pre').addClass('preserve');
@ -126,56 +112,31 @@ function addPreserve() {
// Special use case for codeblocks generated from yaml data / frontmatter
$(this).find('pre').addClass('preserve');
});
}
// Retrieve the currently selected URLs from the urls local storage object.
function getUrls() {
const { cloud, oss, core, enterprise, serverless, dedicated, clustered } = getInfluxDBUrls();
return { oss, cloud, core, enterprise, serverless, dedicated, clustered };
}
// Retrieve the currently selected URLs from the urls local storage object.
function getUrls() {
var storedUrls = getInfluxDBUrls();
var currentCloudUrl = storedUrls.cloud;
var currentOSSUrl = storedUrls.oss;
var currentCoreUrl = storedUrls.core;
var currentEnterpriseUrl = storedUrls.enterprise;
var currentServerlessUrl = storedUrls.serverless;
var currentDedicatedUrl = storedUrls.dedicated;
var currentClusteredUrl = storedUrls.clustered;
var urls = {
oss: currentOSSUrl,
cloud: currentCloudUrl,
core: currentCoreUrl,
enterprise: currentEnterpriseUrl,
serverless: currentServerlessUrl,
dedicated: currentDedicatedUrl,
clustered: currentClusteredUrl,
};
return urls;
}
// Retrieve the previously selected URLs from the from the urls local storage object.
// This is used to update URLs whenever you switch between browser tabs.
function getPrevUrls() {
const {
prev_cloud: cloud,
prev_oss: oss,
prev_core: core,
prev_enterprise: enterprise,
prev_serverless: serverless,
prev_dedicated: dedicated,
prev_clustered: clustered,
} = getInfluxDBUrls();
return { oss, cloud, core, enterprise, serverless, dedicated, clustered };
}
// Retrieve the previously selected URLs from the from the urls local storage object.
// This is used to update URLs whenever you switch between browser tabs.
function getPrevUrls() {
var storedUrls = getInfluxDBUrls();
var prevCloudUrl = storedUrls.prev_cloud;
var prevOSSUrl = storedUrls.prev_oss;
var prevCoreUrl = storedUrls.prev_core;
var prevEnterpriseUrl = storedUrls.prev_enterprise;
var prevServerlessUrl = storedUrls.prev_serverless;
var prevDedicatedUrl = storedUrls.prev_dedicated;
var prevClusteredUrl = storedUrls.prev_clustered;
var prevUrls = {
oss: prevOSSUrl,
cloud: prevCloudUrl,
core: prevCoreUrl,
enterprise: prevEnterpriseUrl,
serverless: prevServerlessUrl,
dedicated: prevDedicatedUrl,
clustered: prevClusteredUrl,
};
return prevUrls;
}
// Iterate through code blocks and update InfluxDB urls
function updateUrls(prevUrls, newUrls) {
var preference = getURLPreference();
// Iterate through code blocks and update InfluxDB urls
function updateUrls(prevUrls, newUrls) {
var prevUrlsParsed = {
oss: {},
cloud: {},
@ -241,29 +202,43 @@ function updateUrls(prevUrls, newUrls) {
{ replace: prevUrlsParsed.clustered, with: newUrlsParsed.clustered },
];
if (context() === 'cloud') {
var replacements = cloudReplacements;
} else if (context() === 'core') {
var replacements = coreReplacements;
} else if (context() === 'enterprise') {
var replacements = enterpriseReplacements;
} else if (context() === 'serverless') {
var replacements = serverlessReplacements;
} else if (context() === 'dedicated') {
var replacements = dedicatedReplacements;
} else if (context() === 'clustered') {
var replacements = clusteredReplacements;
} else if (context() === 'oss/enterprise') {
var replacements = ossReplacements;
} else if (preference === 'cloud') {
var replacements = cloudReplacements;
var replacements;
switch (PRODUCT_CONTEXT) {
case 'cloud':
replacements = cloudReplacements;
break;
case 'core':
replacements = coreReplacements;
break;
case 'enterprise':
replacements = enterpriseReplacements;
break;
case 'serverless':
replacements = serverlessReplacements;
break;
case 'dedicated':
replacements = dedicatedReplacements;
break;
case 'clustered':
replacements = clusteredReplacements;
break;
case 'oss/enterprise':
replacements = ossReplacements;
break;
default:
if (getURLPreference() === 'cloud') {
replacements = cloudReplacements;
} else {
var replacements = ossReplacements;
replacements = ossReplacements;
}
break;
}
replacements.forEach(function (o) {
if (o.replace.origin != o.with.origin) {
var fuzzyOrigin = new RegExp(o.replace.origin + '(:(^443)|[0-9]+)?', 'g');
var fuzzyOrigin = new RegExp(
o.replace.origin + '(:(^443)|[0-9]+)?',
'g'
);
$(elementSelector).each(function () {
$(this).html(
$(this)
@ -307,28 +282,28 @@ function updateUrls(prevUrls, newUrls) {
});
}
});
}
}
// Append the URL selector button to each codeblock containing a placeholder URL
function appendUrlSelector() {
var appendToUrls = [
placeholderUrls.oss,
placeholderUrls.cloud,
placeholderUrls.core,
placeholderUrls.enterprise,
placeholderUrls.serverless,
placeholderUrls.dedicated,
placeholderUrls.clustered,
];
// Append the URL selector button to each codeblock containing a placeholder URL
function appendUrlSelector(urls={
cloud: '',
oss: '',
core: '',
enterprise: '',
serverless: '',
dedicated: '',
clustered: '',
}) {
const appendToUrls = Object.values(urls);
getBtnText = (context) => {
contextText = {
const getBtnText = (context) => {
const contextText = {
'oss/enterprise': 'Change InfluxDB URL',
cloud: 'InfluxDB Cloud Region',
core: 'Change InfluxDB URL',
enterprise: 'Change InfluxDB URL',
serverless: 'InfluxDB Cloud Region',
dedicated: 'Set Dedicated cluster URL',
dedicated: 'Set Cloud Dedicated cluster URL',
clustered: 'Set InfluxDB cluster URL',
other: 'InfluxDB Cloud or OSS?',
};
@ -342,50 +317,51 @@ function appendUrlSelector() {
if (code.includes(url)) {
$(this).after(
"<div class='select-url'><a class='url-trigger' href='#'>" +
getBtnText(context()) +
getBtnText(PRODUCT_CONTEXT) +
'</a></div>'
);
$('.select-url').fadeIn(400);
}
});
});
}
}
////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Function executions //////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////// Initialize InfluxDB URL interactions ////////////////////
////////////////////////////////////////////////////////////////////////////
// Add the preserve tag to code blocks that shouldn't be updated
addPreserve();
// Add the preserve tag to code blocks that shouldn't be updated
addPreserve();
const { cloud, oss, core, enterprise, serverless, dedicated, clustered } = DEFAULT_STORAGE_URLS;
// Append URL selector buttons to code blocks
appendUrlSelector();
// Append URL selector buttons to code blocks
appendUrlSelector({ cloud, oss, core, enterprise, serverless, dedicated, clustered });
// Update URLs on load
updateUrls(placeholderUrls, getUrls());
// Update URLs on load
// Set active radio button on page load
setRadioButtons(getUrls());
updateUrls({ cloud, oss, core, enterprise, serverless, dedicated, clustered }, getUrls());
////////////////////////////////////////////////////////////////////////////////
////////////////////////// Modal window interactions ///////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Set active radio button on page load
setRadioButtons(getUrls());
// General modal window interactions are controlled in modals.js
////////////////////////////////////////////////////////////////////////////////
////////////////////////// Modal window interactions ///////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Open the InfluxDB URL selector modal
$('.url-trigger').click(function (e) {
// General modal window interactions are controlled in modals.js
// Open the InfluxDB URL selector modal
$('.url-trigger').click(function (e) {
e.preventDefault();
toggleModal('#influxdb-url-list');
});
});
// Set the selected URL radio buttons to :checked
function setRadioButtons() {
currentUrls = getUrls();
$('input[name="influxdb-cloud-url"][value="' + currentUrls.cloud + '"]').prop(
'checked',
true
);
// Set the selected URL radio buttons to :checked
function setRadioButtons() {
const currentUrls = getUrls();
$(
'input[name="influxdb-cloud-url"][value="' + currentUrls.cloud + '"]'
).prop('checked', true);
$(
'input[name="influxdb-serverless-url"][value="' +
currentUrls.serverless +
@ -399,154 +375,166 @@ function setRadioButtons() {
'checked',
true
);
$('input[name="influxdb-enterprise-url"][value="' + currentUrls.enterprise + '"]').prop(
'checked',
true
);
}
$(
'input[name="influxdb-enterprise-url"][value="' +
currentUrls.enterprise +
'"]'
).prop('checked', true);
}
// Add checked to fake-radio if cluster is selected on page load
if ($('ul.clusters label input').is(':checked')) {
// Add checked to fake-radio if cluster is selected on page load
if ($('ul.clusters label input').is(':checked')) {
var group = $('ul.clusters label input:checked')
.parent()
.parent()
.parent()
.siblings();
$('.fake-radio', group).addClass('checked');
}
}
// Select first cluster when region is clicked
$('p.region').click(function () {
// Select first cluster when region is clicked
$('p.region').click(function () {
if (!$('.fake-radio', this).hasClass('checked')) {
$('.fake-radio', this).addClass('checked');
$('+ ul.clusters li:first label', this).trigger('click');
}
});
});
// Remove checked class from fake-radio when another region is selected
$('.region-group').click(function () {
// Remove checked class from fake-radio when another region is selected
$('.region-group').click(function () {
if (!$('.fake-radio', this).hasClass('checked')) {
$('.fake-radio', !this).removeClass('checked');
$('.fake-radio', this).addClass('checked');
}
});
});
// Update URLs and URL preference when selected/clicked in the modal
$('input[name="influxdb-oss-url"]').change(function () {
// Update URLs and URL preference when selected/clicked in the modal
$('input[name="influxdb-oss-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('oss', newUrl, getUrls().oss);
updateUrls(getPrevUrls(), getUrls());
setURLPreference('oss');
});
$('input[name="influxdb-oss-url"]').click(function () {
});
$('input[name="influxdb-oss-url"]').click(function () {
setURLPreference('oss');
});
});
$('input[name="influxdb-cloud-url"]').change(function () {
$('input[name="influxdb-cloud-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('cloud', newUrl, getUrls().cloud);
updateUrls(getPrevUrls(), getUrls());
});
$('input[name="influxdb-cloud-url"]').click(function () {
});
$('input[name="influxdb-cloud-url"]').click(function () {
setURLPreference('cloud');
});
});
$('input[name="influxdb-core-url"]').change(function () {
$('input[name="influxdb-core-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('core', newUrl, getUrls().core);
updateUrls(getPrevUrls(), getUrls());
});
});
$('input[name="influxdb-enterprise-url"]').change(function () {
$('input[name="influxdb-enterprise-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('enterprise', newUrl, getUrls().enterprise);
updateUrls(getPrevUrls(), getUrls());
});
});
$('input[name="influxdb-serverless-url"]').change(function () {
$('input[name="influxdb-serverless-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('serverless', newUrl, getUrls().serverless);
updateUrls(getPrevUrls(), getUrls());
});
});
$('input[name="influxdb-dedicated-url"]').change(function () {
$('input[name="influxdb-dedicated-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('dedicated', newUrl, getUrls().dedicated);
updateUrls(getPrevUrls(), getUrls());
});
});
$('input[name="influxdb-clustered-url"]').change(function () {
$('input[name="influxdb-clustered-url"]').change(function () {
var newUrl = $(this).val();
storeUrl('clustered', newUrl, getUrls().clustered);
updateUrls(getPrevUrls(), getUrls());
});
});
// Toggle preference tabs
function togglePrefBtns(el) {
preference = el.length ? el.attr('id').replace('pref-', '') : 'cloud';
prefUrls = $('#' + preference + '-urls');
// Populate the product-specific URL fields on page load
UNIQUE_URL_PRODUCTS.forEach(function (productEl) {
let productUrlCookie = getInfluxDBUrl(productEl);
$(`input#${productEl}-url-field`).val(productUrlCookie);
$(`#${productEl}-url-field`).val(productUrlCookie);
});
// Toggle preference tabs
function togglePrefBtns(el) {
const preference = el.length ? el.attr('id').replace('pref-', '') : 'cloud';
const prefUrls = $('#' + preference + '-urls');
el.addClass('active');
el.siblings().removeClass('active');
prefUrls.addClass('active').removeClass('inactive');
prefUrls.siblings().addClass('inactive').removeClass('active');
setURLPreference(preference);
}
}
// Select preference tab on click
$('#pref-tabs .pref-tab').click(function () {
// Select preference tab on click
$('#pref-tabs .pref-tab').click(function () {
togglePrefBtns($(this));
});
});
// Select preference tab from local storage
function showPreference() {
var preference = getPreference('influxdb_url');
prefTab = $('#pref-' + preference);
// Select preference tab from local storage
function showPreference() {
const preference = getPreference('influxdb_url');
const prefTab = $('#pref-' + preference);
togglePrefBtns(prefTab);
}
}
// Toggled preferred service on load
showPreference();
// Toggled preferred service on load
showPreference();
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Custom URLs //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Custom URLs //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Validate custom URLs
function validateUrl(url) {
// Validate custom URLs
function validateUrl(url) {
/** validDomain = (Named host | IPv6 host | IPvFuture host)(:Port)? **/
var validDomain = new RegExp(
const validDomain = new RegExp(
`([a-z0-9\-._~%]+` +
`|\[[a-f0-9:.]+\]` +
`|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\])` +
`(:[0-9]+)?`
);
if (!['dedicated', 'clustered'].includes(context())) {
if (!IS_UNIQUE_URL_PRODUCT) {
// Validation for non-dedicated, non-clustered custom InfluxDB URLs
try {
new URL(url);
return { valid: true, error: '' };
} catch (e) {
var validProtocol = /^http(s?)/;
var protocol = url.match(/http(s?):\/\//)
const validProtocol = /^http(s?)/;
const protocol = url.match(/http(s?):\/\//)
? url.match(/http(s?):\/\//)[0]
: '';
var domain = url.replace(protocol, '');
const domain = url.replace(protocol, '');
// First use the regex to check for an HTTP protocol and valid domain
// --JS URL validation can't differentiate host:port string from a protocol.
if (validProtocol.test(protocol) == false) {
return { valid: false, error: 'Invalid protocol, use http[s]' };
} else if (validDomain.test(domain) == false) {
return { valid: false, error: 'Invalid domain' };
} else if (e) {
} else {
try {
new URL(url);
return { valid: true, error: '' };
} catch (e) {
if (e instanceof TypeError) {
return { valid: false, error: 'Invalid URL' };
}
}
}
} else {
// Validation for product-specific URLs
var includesProtocol = /^.*:\/\//;
var protocol = url.match(/^.*:\/\//) ? url.match(/^.*:\/\//)[0] : '';
var domain = url.replace(protocol, '');
const includesProtocol = /^.*:\/\//;
const protocol = url.match(/^.*:\/\//) ? url.match(/^.*:\/\//)[0] : '';
const domain = url.replace(protocol, '');
if (url.length === 0) {
return { valid: true, error: '' };
@ -558,29 +546,29 @@ function validateUrl(url) {
return { valid: true, error: '' };
}
}
}
}
// Show validation errors
function showValidationMessage(validation) {
// Show validation errors
function showValidationMessage(validation) {
$('#custom-url').addClass('error');
$('#custom-url').attr('data-message', validation.error);
}
}
// Hide validation messages and replace the message attr with empty string
function hideValidationMessage() {
// Hide validation messages and replace the message attr with empty string
function hideValidationMessage() {
$('#custom-url').removeClass('error').attr('data-message', '');
}
}
// Set the custom URL local storage object and apply the change
// If the custom URL field is empty, it defaults to the context default
function applyCustomUrl() {
// Set the custom URL local storage object and apply the change
// If the custom URL field is empty, it defaults to the context default
function applyCustomUrl() {
var custUrl = $('#custom-url-field').val();
let urlValidation = validateUrl(custUrl);
if (custUrl.length > 0) {
if (urlValidation.valid) {
hideValidationMessage();
storeCustomUrl(custUrl);
storeUrl(context(), custUrl, getUrls()[context()]);
storeUrl(PRODUCT_CONTEXT, custUrl, getUrls()[PRODUCT_CONTEXT]);
updateUrls(getPrevUrls(), getUrls());
} else {
showValidationMessage(urlValidation);
@ -589,21 +577,21 @@ function applyCustomUrl() {
removeCustomUrl();
hideValidationMessage();
$(
'input[name="influxdb-${context()}-url"][value="' + defaultUrls[context()] + '"]'
`input[name="influxdb-${PRODUCT_CONTEXT}-url"][value="${DEFAULT_URLS[PRODUCT_CONTEXT]}"]`
).trigger('click');
}
}
}
// Set the product URL local storage object and apply the change
// If the product URL field is empty, it defaults to the product default
function applyProductUrl(product) {
// Set the product URL local storage object and apply the change
// If the product URL field is empty, it defaults to the product default
function applyProductUrl(product) {
var productUrl = $(`#${product}-url-field`).val();
let urlValidation = validateUrl(productUrl);
if (productUrl.length > 0) {
if (urlValidation.valid) {
hideValidationMessage();
storeProductUrl(product, productUrl);
getUrls(product, productUrl, getUrls()[product]);
storeUrl(product, productUrl, getUrls()[product]);
updateUrls(getPrevUrls(), getUrls());
} else {
showValidationMessage(urlValidation);
@ -612,64 +600,52 @@ function applyProductUrl(product) {
removeProductUrl(product);
hideValidationMessage();
}
}
}
// Trigger radio button on custom URL field focus
$('input#custom-url-field').focus(function (e) {
// Trigger radio button on custom URL field focus
$('input#custom-url-field').focus(function () {
$('input#custom[type="radio"]').trigger('click');
});
});
// Update URLs and close modal when using 'enter' to exit custom URL field
$('#custom-url').submit(function (e) {
// Update URLs and close modal when using 'enter' to exit custom URL field
$('#custom-url').submit(function (e) {
e.preventDefault();
const productContext = context();
let url = $('#custom-url-field').val() || '';
if (['dedicated', 'clustered'].includes(productContext)) {
url = $(`#${productContext}-url-field`).val() || '';
if (['dedicated', 'clustered'].includes(PRODUCT_CONTEXT)) {
url = $(`#${PRODUCT_CONTEXT}-url-field`).val() || '';
}
const urlValidation = validateUrl(url);
if (url === '' || urlValidation.valid) {
if (!['dedicated', 'clustered'].includes(productContext)) {
if (!['dedicated', 'clustered'].includes(PRODUCT_CONTEXT)) {
applyCustomUrl();
} else {
applyProductUrl(productContext);
applyProductUrl(PRODUCT_CONTEXT);
}
$('#modal-close').trigger('click');
} else {
showValidationMessage(urlValidation);
}
});
});
// List of elements that store custom URLs
var urlValueElements = [
// List of elements that store custom URLs
var urlValueElements = [
'#custom-url-field',
'#dedicated-url-field',
'#clustered-url-field',
].join();
].join();
// Store the custom InfluxDB URL or product-specific URL when exiting the field
$(urlValueElements).blur(function () {
!['dedicated', 'clustered'].includes(context())
// Store the custom InfluxDB URL or product-specific URL when exiting the field
$(urlValueElements).blur(function () {
!['dedicated', 'clustered'].includes(PRODUCT_CONTEXT)
? applyCustomUrl()
: applyProductUrl(context());
});
: applyProductUrl(PRODUCT_CONTEXT);
});
/** Delay execution of a function `fn` for a number of milliseconds `ms`
* e.g., delay a validation handler to avoid annoying the user.
*/
function delay(fn, ms) {
let timer = 0;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), ms || 0);
};
}
function handleUrlValidation() {
function handleUrlValidation() {
let url = $(urlValueElements).val();
let urlValidation = validateUrl(url);
if (urlValidation.valid) {
@ -677,40 +653,37 @@ function handleUrlValidation() {
} else {
showValidationMessage(urlValidation);
}
}
// When in erred state, revalidate custom URL on keyup
$(document).on('keyup', urlValueElements, delay(handleUrlValidation, 500));
}
// When in erred state, revalidate custom URL on keyup
$(document).on('keyup', urlValueElements, delay(handleUrlValidation, 500));
// Populate the custom InfluxDB URL field on page load
var customUrlOnLoad = getInfluxDBUrl('custom');
if (customUrlOnLoad != '') {
// Populate the custom InfluxDB URL field on page load
var customUrlOnLoad = getInfluxDBUrl('custom');
if (customUrlOnLoad != '') {
$('input#custom').val(customUrlOnLoad);
$('#custom-url-field').val(customUrlOnLoad);
}
}
// Populate the product-specific URL fields on page load
var productsWithUniqueURLs = ['dedicated', 'clustered'];
// Populate the product-specific URL fields on page load
var productsWithUniqueURLs = ['dedicated', 'clustered'];
productsWithUniqueURLs.forEach(function (productEl) {
productUrlCookie = getInfluxDBUrl(productEl);
productsWithUniqueURLs.forEach(function (productEl) {
const productUrlCookie = getInfluxDBUrl(productEl);
$(`input#${productEl}-url-field`).val(productUrlCookie);
$(`#${productEl}-url-field`).val(productUrlCookie);
});
});
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// Dynamically update URLs ////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// Dynamically update URLs ////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Extract the protocol and hostname of referrer
referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g);
referrerHost = referrerMatch ? referrerMatch[0] : '';
// Check if the referrerHost is one of the cloud URLs
// cloudUrls is built dynamically in layouts/partials/footer/javascript.html
if (cloudUrls.includes(referrerHost)) {
// Check if the referrerHost is one of the cloud URLs
// cloudUrls is built dynamically in layouts/partials/footer/javascript.html
if (CLOUD_URLS.includes(referrerHost)) {
storeUrl('cloud', referrerHost, getUrls().cloud);
updateUrls(getPrevUrls(), getUrls());
setRadioButtons();
setURLPreference('cloud');
showPreference();
}
}

View File

@ -10,6 +10,7 @@
- messages: Messages (data/notifications.yaml) that have been seen (array)
- callouts: Feature callouts that have been seen (array)
*/
import * as pageParams from '@params';
// Prefix for all InfluxData docs local storage
const storagePrefix = 'influxdata_docs_';
@ -17,14 +18,14 @@ const storagePrefix = 'influxdata_docs_';
/*
Initialize data in local storage with a default value.
*/
initializeLocalStorage = (storageKey, defaultValue) => {
fullStorageKey = storagePrefix + storageKey;
function initializeStorageItem(storageKey, defaultValue) {
const fullStorageKey = storagePrefix + storageKey;
// Check if the data exists before initializing the data
if (localStorage.getItem(fullStorageKey) === null) {
localStorage.setItem(fullStorageKey, defaultValue);
}
};
}
/*
////////////////////////////////////////////////////////////////////////////////
@ -35,7 +36,7 @@ initializeLocalStorage = (storageKey, defaultValue) => {
const prefStorageKey = storagePrefix + 'preferences';
// Default preferences
var defaultPrefObj = {
const defaultPrefObj = {
api_lib: null,
influxdb_url: 'cloud',
sidebar_state: 'open',
@ -48,119 +49,113 @@ var defaultPrefObj = {
Retrieve a preference from the preference key.
If the key doesn't exist, initialize it with default values.
*/
getPreference = prefName => {
function getPreference(prefName) {
// Initialize preference data if it doesn't already exist
if (localStorage.getItem(prefStorageKey) === null) {
initializeLocalStorage('preferences', JSON.stringify(defaultPrefObj));
initializeStorageItem('preferences', JSON.stringify(defaultPrefObj));
}
// Retrieve and parse preferences as JSON
prefString = localStorage.getItem(prefStorageKey);
prefObj = JSON.parse(prefString);
const prefString = localStorage.getItem(prefStorageKey);
const prefObj = JSON.parse(prefString);
// Return the value of the specified preference
return prefObj[prefName];
};
}
// Set a preference in the preferences key
setPreference = (prefID, prefValue) => {
var prefString = localStorage.getItem(prefStorageKey);
let prefObj = JSON.parse(prefString);
function setPreference(prefID, prefValue) {
const prefString = localStorage.getItem(prefStorageKey);
const prefObj = JSON.parse(prefString);
prefObj[prefID] = prefValue;
localStorage.setItem(prefStorageKey, JSON.stringify(prefObj));
};
}
// Return an object containing all preferences
getPreferences = () => JSON.parse(localStorage.getItem(prefStorageKey));
function getPreferences() {
return JSON.parse(localStorage.getItem(prefStorageKey));
}
/*
////////////////////////////////////////////////////////////////////////////////
///////////////////////////// INFLUXDATA DOCS URLS /////////////////////////////
//////////// MANAGE INFLUXDATA DOCS URLS IN LOCAL STORAGE //////////////////////
////////////////////////////////////////////////////////////////////////////////
*/
const urlStorageKey = storagePrefix + 'urls';
// Default URLs per product
var defaultUrls = {
oss: 'http://localhost:8086',
cloud: 'https://us-west-2-1.aws.cloud2.influxdata.com',
core: 'http://localhost:8181',
enterprise: 'http://localhost:8181',
serverless: 'https://us-east-1-1.aws.cloud2.influxdata.com',
dedicated: 'cluster-id.a.influxdb.io',
clustered: 'cluster-host.com',
};
const defaultUrls = {};
Object.entries(pageParams.influxdb_urls).forEach(([product, {providers}]) => {
defaultUrls[product] = providers.filter(provider => provider.name === 'Default')[0]?.regions[0]?.url;
});
// Defines the default urls value
var defaultUrlsObj = {
export const DEFAULT_STORAGE_URLS = {
oss: defaultUrls.oss,
cloud: defaultUrls.cloud,
serverless: defaultUrls.serverless,
core: defaultUrls.core,
enterprise: defaultUrls.enterprise,
dedicated: defaultUrls.dedicated,
dedicated: defaultUrls.cloud_dedicated,
clustered: defaultUrls.clustered,
prev_oss: defaultUrls.oss,
prev_cloud: defaultUrls.cloud,
prev_core: defaultUrls.core,
prev_enterprise: defaultUrls.enterprise,
prev_serverless: defaultUrls.serverless,
prev_dedicated: defaultUrls.dedicated,
prev_dedicated: defaultUrls.cloud_dedicated,
prev_clustered: defaultUrls.clustered,
custom: '',
};
const urlStorageKey = storagePrefix + 'urls';
// Return an object that contains all InfluxDB urls stored in the urls key
getInfluxDBUrls = () => {
function getInfluxDBUrls() {
// Initialize urls data if it doesn't already exist
if (localStorage.getItem(urlStorageKey) === null) {
initializeLocalStorage('urls', JSON.stringify(defaultUrlsObj));
initializeStorageItem('urls', JSON.stringify(DEFAULT_STORAGE_URLS));
}
return JSON.parse(localStorage.getItem(urlStorageKey));
};
}
// Get the current or previous URL for a specific product or a custom url
getInfluxDBUrl = product => {
function getInfluxDBUrl(product) {
// Initialize urls data if it doesn't already exist
if (localStorage.getItem(urlStorageKey) === null) {
initializeLocalStorage('urls', JSON.stringify(defaultUrlsObj));
initializeStorageItem('urls', JSON.stringify(DEFAULT_STORAGE_URLS));
}
// Retrieve and parse the URLs as JSON
urlsString = localStorage.getItem(urlStorageKey);
urlsObj = JSON.parse(urlsString);
const urlsString = localStorage.getItem(urlStorageKey);
const urlsObj = JSON.parse(urlsString);
// Return the URL of the specified product
return urlsObj[product];
};
}
/*
Set multiple product URLs in the urls key.
Input should be an object where the key is the product and the value is the
URL to set for that product.
*/
setInfluxDBUrls = updatedUrlsObj => {
var urlsString = localStorage.getItem(urlStorageKey);
let urlsObj = JSON.parse(urlsString);
function setInfluxDBUrls(updatedUrlsObj) {
const urlsString = localStorage.getItem(urlStorageKey);
const urlsObj = JSON.parse(urlsString);
newUrlsObj = { ...urlsObj, ...updatedUrlsObj };
const newUrlsObj = { ...urlsObj, ...updatedUrlsObj };
localStorage.setItem(urlStorageKey, JSON.stringify(newUrlsObj));
};
}
// Set an InfluxDB URL to an empty string in the urls key
removeInfluxDBUrl = product => {
var urlsString = localStorage.getItem(urlStorageKey);
let urlsObj = JSON.parse(urlsString);
function removeInfluxDBUrl(product) {
const urlsString = localStorage.getItem(urlStorageKey);
const urlsObj = JSON.parse(urlsString);
urlsObj[product] = '';
localStorage.setItem(urlStorageKey, JSON.stringify(urlsObj));
};
}
/*
////////////////////////////////////////////////////////////////////////////////
@ -171,24 +166,24 @@ removeInfluxDBUrl = product => {
const notificationStorageKey = storagePrefix + 'notifications';
// Default notifications
var defaultNotificationsObj = {
const defaultNotificationsObj = {
messages: [],
callouts: [],
};
getNotifications = () => {
function getNotifications() {
// Initialize notifications data if it doesn't already exist
if (localStorage.getItem(notificationStorageKey) === null) {
initializeLocalStorage('notifications', JSON.stringify(defaultNotificationsObj));
initializeStorageItem('notifications', JSON.stringify(defaultNotificationsObj));
}
// Retrieve and parse the notifications data as JSON
notificationString = localStorage.getItem(notificationStorageKey);
notificationObj = JSON.parse(notificationString);
const notificationString = localStorage.getItem(notificationStorageKey);
const notificationObj = JSON.parse(notificationString);
// Return the notifications object
return notificationObj;
};
}
/*
Checks if a notification is read. Provide the notification ID and one of the
@ -200,12 +195,12 @@ getNotifications = () => {
If the notification ID exists in the array assigned to the specified type, the
notification has been read.
*/
notificationIsRead = (notificationID, notificationType) => {
let notificationsObj = getNotifications();
readNotifications = notificationsObj[`${notificationType}s`];
function notificationIsRead(notificationID, notificationType) {
const notificationsObj = getNotifications();
const readNotifications = notificationsObj[`${notificationType}s`];
return readNotifications.includes(notificationID);
};
}
/*
Sets a notification as read. Provide the notification ID and one of the
@ -216,12 +211,28 @@ notificationIsRead = (notificationID, notificationType) => {
The notification ID is added to the array assigned to the specified type.
*/
setNotificationAsRead = (notificationID, notificationType) => {
let notificationsObj = getNotifications();
let readNotifications = notificationsObj[`${notificationType}s`];
function setNotificationAsRead(notificationID, notificationType) {
const notificationsObj = getNotifications();
const readNotifications = notificationsObj[`${notificationType}s`];
readNotifications.push(notificationID);
notificationsObj[notificationType + 's'] = readNotifications;
localStorage.setItem(notificationStorageKey, JSON.stringify(notificationsObj));
}
// Export functions as a module and make the file backwards compatible for non-module environments until all remaining dependent scripts are ported to modules
export {
defaultUrls,
initializeStorageItem,
getPreference,
setPreference,
getPreferences,
getInfluxDBUrls,
getInfluxDBUrl,
setInfluxDBUrls,
removeInfluxDBUrl,
getNotifications,
notificationIsRead,
setNotificationAsRead,
};

141
assets/js/main.js Normal file
View File

@ -0,0 +1,141 @@
// assets/js/main.js
// If you need to pass parameters from the calling Hugo page, you can import them here like so:
// import * as pageParams from '@params';
/** Import modules that are not components.
* TODO: Refactor these into single-purpose component modules.
*/
// import * as codeblocksPreferences from './api-libs.js';
// import * as datetime from './datetime.js';
// import * as featureCallouts from './feature-callouts.js';
import * as apiLibs from './api-libs.js';
import * as codeControls from './code-controls.js';
import * as contentInteractions from './content-interactions.js';
import { delay } from './helpers.js';
import { InfluxDBUrl } from './influxdb-url.js';
import * as localStorage from './local-storage.js';
import * as modals from './modals.js';
import * as notifications from './notifications.js';
import * as pageContext from './page-context.js';
import * as pageFeedback from './page-feedback.js';
import * as tabbedContent from './tabbed-content.js';
import * as v3Wayfinding from './v3-wayfinding.js';
// import * as homeInteractions from './home-interactions.js';
// import { getUrls, getReferrerHost, InfluxDBUrl } from './influxdb-url.js';
// import * as keybindings from './keybindings.js';
// import * as listFilters from './list-filters.js';
// import { Modal } from './modal.js';
// import { showNotifications } from './notifications.js';
// import ReleaseTOC from './release-toc.js';
// import * as scroll from './scroll.js';
// import { TabbedContent } from './tabbed-content.js';
/** Import component modules
* The component pattern organizes JavaScript, CSS, and HTML for a specific UI element or interaction:
* - HTML: my-component.html
* - CSS: my-component.css
* - JavaScript: my-component.js
* The JavaScript is ideally a single-purpose module that exports a single default function to initialize the component and handle any component interactions.
*/
import AskAITrigger from './ask-ai-trigger.js';
import { CustomTimeTrigger } from './custom-timestamps.js';
import { SearchButton } from './search-button.js';
import { SidebarToggle } from './sidebar-toggle.js';
import Theme from './theme.js';
import ThemeSwitch from './theme-switch.js';
// import CodeControls from './code-controls.js';
// import ContentInteractions from './content-interactions.js';
// import CustomTimestamps from './custom-timestamps.js';
// import Diagram from './Diagram.js';
// import FluxGroupKeysExample from './FluxGroupKeysExample.js';
// import FluxInfluxDBVersionsModal from './flux-influxdb-versions.js';
// import PageFeedback from './page-feedback.js';
// import SearchInput from './SearchInput.js';
// import Sidebar from './Sidebar.js';
// import V3Wayfinding from './v3-wayfinding.js';
// import VersionSelector from './VersionSelector.js';
// Expose libraries and components within a namespaced object (for backwards compatibility or testing)
// Expose libraries and components within a namespaced object (for backwards compatibility or testing)
document.addEventListener('DOMContentLoaded', function () {
if (typeof window.influxdatadocs === 'undefined') {
window.influxdatadocs = {};
}
// Expose modules to the global object for debugging, testing, and backwards compatibility for non-ES6 modules.
window.influxdatadocs.delay = delay;
window.influxdatadocs.localStorage = window.LocalStorageAPI = localStorage;
window.influxdatadocs.pageContext = pageContext;
window.influxdatadocs.toggleModal = modals.toggleModal;
// On content loaded, initialize (not-component-ready) UI interaction modules
// To differentiate these from component-ready modules, these modules typically export an initialize function that wraps UI interactions and event listeners.
modals.initialize();
apiLibs.initialize();
codeControls.initialize();
contentInteractions.initialize();
InfluxDBUrl();
notifications.initialize();
pageFeedback.initialize();
tabbedContent.initialize();
v3Wayfinding.initialize();
/** Initialize components
Component Structure: Each component is structured as a jQuery anonymous function that listens for the document ready state.
Initialization in main.js: Each component is called in main.js inside a jQuery document ready function to ensure they are initialized when the document is ready.
Note: These components should *not* be called directly in the HTML.
*/
const components = document.querySelectorAll('[data-component]');
components.forEach((component) => {
const componentName = component.getAttribute('data-component');
switch (componentName) {
case 'ask-ai-trigger':
AskAITrigger({ component });
window.influxdatadocs[componentName] = AskAITrigger;
break;
case 'custom-time-trigger':
CustomTimeTrigger({ component });
window.influxdatadocs[componentName] = CustomTimeTrigger;
break;
case 'search-button':
SearchButton({ component });
window.influxdatadocs[componentName] = SearchButton;
break;
case 'sidebar-toggle':
SidebarToggle({ component });
window.influxdatadocs[componentName] = SidebarToggle;
break;
case 'theme':
Theme({ component });
window.influxdatadocs[componentName] = Theme;
break;
// CodeControls();
// ContentInteractions();
// CustomTimestamps();
// Diagram();
// FluxGroupKeysExample();
// FluxInfluxDBVersionsModal();
// InfluxDBUrl();
// Modal();
// PageFeedback();
// ReleaseTOC();
// SearchInput();
// showNotifications();
// Sidebar();
// TabbedContent();
// ThemeSwitch({});
// V3Wayfinding();
// VersionSelector();
case 'theme-switch':
ThemeSwitch({ component });
window.influxdatadocs[componentName] = ThemeSwitch;
break;
default:
console.warn(`Unknown component: ${componentName}`);
}
});
});

View File

@ -2,21 +2,19 @@
/////////////////////// General modal window interactions //////////////////////
////////////////////////////////////////////////////////////////////////////////
// Toggle the URL selector modal window
function toggleModal(modalID="") {
if ($(".modal").hasClass("open")) {
$(".modal").fadeOut(200).removeClass("open");
$(".modal-content").delay(400).hide(0);
} else {
$(".modal").fadeIn(200).addClass("open");
$(`${modalID}.modal-content`).show();
}
}
import $ from 'jquery';
// Close modal window on click
$("#modal-close, .modal-overlay").click(function(e) {
e.preventDefault()
toggleModal()
function handleModalClick() {
// Open modal window on click
$('.modal-trigger').click(function (e) {
e.preventDefault();
toggleModal();
});
// Close modal window on click
$('#modal-close, .modal-overlay').click(function (e) {
e.preventDefault();
toggleModal();
// Remove modal query param ('view') if it exists
const queryParams = new URLSearchParams(window.location.search);
@ -25,5 +23,22 @@ $("#modal-close, .modal-overlay").click(function(e) {
if (queryParams.get('view') !== null) {
queryParams.delete('view');
window.history.replaceState({}, '', `${location.pathname}${anchor}`);
};
})
}
});
}
function toggleModal(modalID = '') {
if ($('.modal').hasClass('open')) {
$('.modal').fadeOut(200).removeClass('open');
$('.modal-content').delay(400).hide(0);
} else {
$('.modal').fadeIn(200).addClass('open');
$(`${modalID}.modal-content`).show();
}
}
function initialize() {
handleModalClick();
}
export { initialize, toggleModal };

View File

@ -5,6 +5,8 @@
IDs in the messages array are considered read and no longer appear to the user.
*/
import $ from 'jquery';
// Get notification ID
function notificationID(el) {
return $(el).attr('id');
@ -39,7 +41,10 @@ function showNotifications() {
var exclude = $(this).data('exclude').split(',');
var pageInScope = inScope(window.location.pathname, scope);
var pageExcluded = excludePage(window.location.pathname, exclude);
var notificationRead = notificationIsRead(notificationID(this), 'message');
var notificationRead = window.LocalStorageAPI.notificationIsRead(
notificationID(this),
'message'
);
if (pageInScope && !pageExcluded && !notificationRead) {
$(this).show().animate({ right: 0, opacity: 1 }, 200, 'swing');
@ -53,32 +58,43 @@ function hideNotification(el) {
.closest('.notification')
.animate({ height: 0, opacity: 0 }, 200, 'swing', function () {
$(this).hide();
setNotificationAsRead(notificationID(this), 'message');
window.LocalStorageAPI.setNotificationAsRead(
notificationID(this),
'message'
);
});
}
// Show notifications on page load
showNotifications();
function initialize() {
// Show notifications on page load
showNotifications();
// Hide a notification and set the notification as read
$('.close-notification').click(function (e) {
// Hide a notification and set the notification as read
$('.close-notification').click(function (e) {
e.preventDefault();
hideNotification(this);
});
});
$('.notification .show').click(function () {
$('.notification .show').click(function () {
$(this).closest('.notification').toggleClass('min');
});
});
// Notification element scroll position
const notificationsInitialPosition = parseInt(
// Notification element scroll position
const notificationsInitialPosition = parseInt(
$('#docs-notifications').css('top'),
10
);
$(window).scroll(function () {
);
$(window).scroll(function () {
var notificationPosition =
notificationsInitialPosition - scrollY > 10
? notificationsInitialPosition - scrollY
: 10;
$('#docs-notifications').css('top', notificationPosition);
});
});
}
export {
initialize,
showNotifications,
hideNotification,
}

88
assets/js/page-context.js Normal file
View File

@ -0,0 +1,88 @@
/** This module retrieves browser context information and site data for the
* current page, version, and product.
*/
import { products, influxdb_urls } from '@params';
function getCurrentProductData() {
const path = window.location.pathname;
const mappings = [
{ pattern: /\/influxdb\/cloud\//, product: products.cloud, urls: influxdb_urls.influxdb_cloud },
{ pattern: /\/influxdb3\/core/, product: products.influxdb3_core, urls: influxdb_urls.core },
{ pattern: /\/influxdb3\/enterprise/, product: products.influxdb3_enterprise, urls: influxdb_urls.enterprise },
{ pattern: /\/influxdb3\/cloud-serverless/, product: products.influxdb3_cloud_serverless, urls: influxdb_urls.cloud },
{ pattern: /\/influxdb3\/cloud-dedicated/, product: products.influxdb3_cloud_dedicated, urls: influxdb_urls.dedicated },
{ pattern: /\/influxdb3\/clustered/, product: products.influxdb3_clustered, urls: influxdb_urls.clustered },
{ pattern: /\/enterprise_v1\//, product: products.enterprise_influxdb, urls: influxdb_urls.oss },
{ pattern: /\/influxdb.*v1\//, product: products.influxdb, urls: influxdb_urls.oss },
{ pattern: /\/influxdb.*v2\//, product: products.influxdb, urls: influxdb_urls.oss },
{ pattern: /\/kapacitor\//, product: products.kapacitor, urls: influxdb_urls.oss },
{ pattern: /\/telegraf\//, product: products.telegraf, urls: influxdb_urls.oss },
{ pattern: /\/chronograf\//, product: products.chronograf, urls: influxdb_urls.oss },
];
for (const { pattern, product, urls } of mappings) {
if (pattern.test(path)) {
return { product, urls };
}
}
return 'other';
}
// Return the page context (cloud, serverless, oss/enterprise, dedicated, clustered, other)
function getContext() {
if (/\/influxdb\/cloud\//.test(window.location.pathname)) {
return 'cloud';
} else if (/\/influxdb3\/core/.test(window.location.pathname)) {
return 'core';
} else if (/\/influxdb3\/enterprise/.test(window.location.pathname)) {
return 'enterprise';
} else if (/\/influxdb3\/cloud-serverless/.test(window.location.pathname)) {
return 'serverless';
} else if (/\/influxdb3\/cloud-dedicated/.test(window.location.pathname)) {
return 'dedicated';
} else if (/\/influxdb3\/clustered/.test(window.location.pathname)) {
return 'clustered';
} else if (
/\/(enterprise_|influxdb).*\/v[1-2]\//.test(window.location.pathname)
) {
return 'oss/enterprise';
} else {
return 'other';
}
}
// Store the host value for the current page
const currentPageHost = window.location.href.match(/^(?:[^/]*\/){2}[^/]+/g)[0];
function getReferrerHost() {
// Extract the protocol and hostname of referrer
const referrerMatch = document.referrer.match(/^(?:[^/]*\/){2}[^/]+/g);
return referrerMatch ? referrerMatch[0] : '';
}
const context = getContext(),
host = currentPageHost,
hostname = location.hostname,
path = location.pathname,
pathArr = location.pathname.split('/').slice(1, -1),
product = pathArr[0],
productData = getCurrentProductData(),
protocol = location.protocol,
referrer = document.referrer === '' ? 'direct' : document.referrer,
referrerHost = getReferrerHost(),
// TODO: Verify this still does what we want since the addition of InfluxDB 3 naming and the Core and Enterprise versions.
version = (/^v\d/.test(pathArr[1]) || pathArr[1]?.includes('cloud') ? pathArr[1].replace(/^v/, '') : "n/a")
export {
context,
host,
hostname,
path,
product,
productData,
protocol,
referrer,
referrerHost,
version,
};

View File

@ -3,52 +3,58 @@
* buttons and modal.
*/
// Collect data from the page path
const pathArr = location.pathname.split('/').slice(1, -1)
const pageData = {
host: location.hostname,
path: location.pathname,
product: pathArr[0],
version: (/^v\d/.test(pathArr[1]) || pathArr[1]?.includes('cloud') ? pathArr[1].replace(/^v/, '') : "n/a"),
}
import $ from 'jquery';
import { hostname, path, product, protocol, version } from './page-context.js';
import { toggleModal } from './modals.js';
// Hijack form submission and send feedback data to be stored.
// Called by onSubmit in each feedback form.
function submitFeedbackForm(formID) {
// Collect form data, structure as an object, and remove fname honeypot
const formData = new FormData(document.forms[formID]);
const formDataObj = Object.fromEntries(formData.entries());
const {fname, ...feedbackData} = formDataObj;
const { ...feedbackData } = formDataObj;
// Build lp fields from form data
let fields = "";
let fields = '';
for (let key in feedbackData) {
// Strip out newlines and escape double quotes if the field key is "feedback"
if (key == "feedback-text") {
fields += key + '="' + feedbackData[key].replace(/(\r\n|\n+|\r+)/gm, " ").replace(/(\")/gm, '\\"') + '",';
if (key == 'feedback-text') {
fields +=
key +
'="' +
feedbackData[key]
.replace(/(\r\n|\n+|\r+)/gm, ' ')
.replace(/(\")/gm, '\\"') +
'",';
} else {
fields += key + "=" + feedbackData[key] + ",";
fields += key + '=' + feedbackData[key] + ',';
}
}
fields = fields.substring(0, fields.length -1);
fields = fields.substring(0, fields.length - 1);
// Build lp using page data and the fields string
const lp = `feedback,host=${pageData.host},path=${pageData.path},product=${pageData.product},version=${pageData.version} ${fields}`
const lp = `feedback,host=${hostname},path=${path},product=${product},version=${version} ${fields}`;
// Use a honeypot form field to detect a bot
// If the value of the honeypot field is greater than 0, the submitter is a bot
function isBot() {
const honeypot = formData.get('fname');
return (honeypot.length > 0)
return honeypot.length > 0;
}
// If the submitter is not a bot, send the feedback data
if (!isBot()) {
xhr = new XMLHttpRequest();
xhr.open('POST', 'https://j32dswat7l.execute-api.us-east-1.amazonaws.com/prod');
const xhr = new XMLHttpRequest();
xhr.open(
'POST',
'https://j32dswat7l.execute-api.us-east-1.amazonaws.com/prod'
);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Access-Control-Allow-Origin', `${location.protocol}//${location.host}`);
xhr.setRequestHeader(
'Access-Control-Allow-Origin',
`${protocol}//${location.host}`
);
xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
xhr.setRequestHeader('Accept', 'application/json');
xhr.send(lp);
@ -70,28 +76,43 @@ function submitLifeCycle() {
// Called by onclick in the page-feedback modal submit button.
function submitLifeCycleAndClose() {
submitFeedbackForm('pagefeedbacktext');
$('.modal #page-feedback .loader-wrapper').css('display', 'flex').hide().fadeIn(200);
$('.modal #page-feedback #thank-you').css('display', 'flex').hide().delay(800).fadeIn(200);
$('.modal #page-feedback textarea').css('box-shadow', 'none')
$('.modal #page-feedback .loader-wrapper')
.css('display', 'flex')
.hide()
.fadeIn(200);
$('.modal #page-feedback #thank-you')
.css('display', 'flex')
.hide()
.delay(800)
.fadeIn(200);
$('.modal #page-feedback textarea').css('box-shadow', 'none');
$('.modal #page-feedback .loader-wrapper').delay(1000).hide(0);
setTimeout(function() {toggleModal()}, 1800);
setTimeout(function () {
toggleModal();
}, 1800);
return false;
}
//////////////////////////////// Event triggers ////////////////////////////////
function initialize() {
//////////////////////////////// Event triggers ////////////////////////////////
// Submit page feedback (yes/no) on radio select and trigger life cycle
$('#pagefeedback input[type=radio]').change(function() {
// Submit page feedback (yes/no) on radio select and trigger life cycle
$('#pagefeedback input[type=radio]').change(function () {
$('form#pagefeedback').submit();
submitLifeCycle()
})
submitLifeCycle();
});
// Toggle the feedback modal when user selects that the page is not helpful
$('#pagefeedback #not-helpful input[type=radio]').click(function() {
setTimeout(function() {toggleModal('#page-feedback')}, 400);
})
// Toggle the feedback modal when user selects that the page is not helpful
$('#pagefeedback #not-helpful input[type=radio]').click(function () {
setTimeout(function () {
toggleModal('#page-feedback');
}, 400);
});
// Toggle the feedback modal when user selects that the page is not helpful
$('.modal #no-thanks').click(function() {
// Toggle the feedback modal when user selects that the page is not helpful
$('.modal #no-thanks').click(function () {
toggleModal();
})
});
}
export { initialize };

View File

@ -0,0 +1,10 @@
import { toggleSidebar } from './sidebar-toggle.js';
export function SearchButton({ component }) {
component.querySelector('[data-action="toggle"]')
.addEventListener('click', () => {
toggleSidebar('sidebar-open');
document.getElementById('algolia-search-input').focus();
return false;
});
}

View File

@ -1,8 +1,10 @@
/*
Copied and pasted this script for CSS swaps w/ cookies from
Portions of this code come from CSS swaps w/ cookies from
http://www.thesitewizard.com/javascripts/change-style-sheets.shtml
*/
import * as localStorage from './local-storage.js';
// *** TO BE CUSTOMISED ***
var sidebar_state_preference_name = 'sidebar_state';
var sidebar_state_duration = 30;
@ -11,7 +13,7 @@ var style_domain = 'docs.influxdata.com';
// *** END OF CUSTOMISABLE SECTION ***
// You do not need to customise anything below this line
function toggleSidebar (toggle_state) {
function toggleSidebar(toggle_state) {
// You may use this script on your site free of charge provided
// you do not remove this notice or the URL below. Script from
// http://www.thesitewizard.com/javascripts/change-style-sheets.shtml
@ -30,16 +32,30 @@ function toggleSidebar (toggle_state) {
link_tag[i].disabled = false;
}
}
setPreference(
localStorage.setPreference(
sidebar_state_preference_name,
toggle_state.replace(/sidebar-/, '')
);
}
}
function setSidebarState () {
var toggle_state = `sidebar-${getPreference(sidebar_state_preference_name)}`;
function setSidebarState() {
var toggle_state = `sidebar-${localStorage.getPreference(sidebar_state_preference_name)}`;
if (toggle_state !== undefined) {
toggleSidebar(toggle_state);
}
}
function SidebarToggle({ component }) {
const current_state = component.getAttribute('data-state');
component
.querySelector('[data-action="toggle"]')
.addEventListener('click', () => {
toggleSidebar(`sidebar-${current_state}`);
return false;
});
setSidebarState();
}
export { setSidebarState, toggleSidebar, SidebarToggle };

View File

@ -5,7 +5,10 @@
* smoothscroll when clicked. The whitelist is defined in content-interactions.js.
**/
function tabbedContent (container, tab, content) {
import $ from 'jquery';
import { scrollToAnchor } from './content-interactions.js';
function tabbedContent(container, tab, content) {
// Add the active class to the first tab in each tab group,
// in case it wasn't already set in the markup.
$(container).each(function () {
@ -30,10 +33,7 @@ function tabbedContent (container, tab, content) {
});
}
tabbedContent('.code-tabs-wrapper', '.code-tabs p a', '.code-tab-content');
tabbedContent('.tabs-wrapper', '.tabs p a', '.tab-content');
function getTabQueryParam () {
function getTabQueryParam() {
const queryParams = new URLSearchParams(window.location.search);
return $('<textarea />').html(queryParams.get('t')).text();
}
@ -41,7 +41,7 @@ function getTabQueryParam () {
// Add query param to .keep-tab paginated navigation buttons to persist tab
// selection when navigating between the pages.
function updateBtnURLs (tabId, op = 'update') {
function updateBtnURLs(tabId, op = 'update') {
$('a.keep-tab').each(function () {
var link = $(this)[0].href;
var tabStr = tabId.replace(/ /, '+');
@ -54,7 +54,7 @@ function updateBtnURLs (tabId, op = 'update') {
});
}
function activateTabs (selector, tab) {
function activateTabs(selector, tab) {
var anchor = window.location.hash;
if (tab !== '') {
let targetTab = $(`${selector} a:contains("${tab}")`);
@ -74,7 +74,11 @@ function activateTabs (selector, tab) {
}
}
$(`.tabs p a, .code-tabs p a`).click(function () {
function initialize() {
tabbedContent('.code-tabs-wrapper', '.code-tabs p a', '.code-tab-content');
tabbedContent('.tabs-wrapper', '.tabs p a', '.tab-content');
$(`.tabs p a, .code-tabs p a`).click(function () {
var queryParams = new URLSearchParams(window.location.search);
var anchor = window.location.hash;
@ -91,12 +95,15 @@ $(`.tabs p a, .code-tabs p a`).click(function () {
window.history.replaceState({}, '', `${location.pathname}${anchor}`);
updateBtnURLs($(this).html(), 'delete');
}
});
});
//////////////////// Activate Tab with Cookie or Query Param ///////////////////
//////////////////// Activate Tab with Cookie or Query Param ///////////////////
tab = getTabQueryParam();
['.tabs', '.code-tabs'].forEach(
selector => activateTabs(selector, tab),
const tab = getTabQueryParam();
['.tabs', '.code-tabs'].forEach(
(selector) => activateTabs(selector, tab),
updateBtnURLs(tab)
);
);
}
export { activateTabs, initialize, tabbedContent, updateBtnURLs };

20
assets/js/theme-switch.js Normal file
View File

@ -0,0 +1,20 @@
import Theme from './theme.js';
export default function ThemeSwitch({ component }) {
if ( component == undefined) {
component = document;
}
component.querySelectorAll(`.theme-switch-light`).forEach((button) => {
button.addEventListener('click', function(event) {
event.preventDefault();
Theme({ style: 'light-theme' });
});
});
component.querySelectorAll(`.theme-switch-dark`).forEach((button) => {
button.addEventListener('click', function(event) {
event.preventDefault();
Theme({ style: 'dark-theme' });
});
});
}

42
assets/js/theme.js Normal file
View File

@ -0,0 +1,42 @@
import { getPreference, setPreference } from './local-storage.js';
const PROPS = {
style_preference_name: 'theme',
style_cookie_duration: 30, // number of days
style_domain: 'docs.influxdata.com',
};
function getPreferredTheme () {
return `${getPreference(PROPS.style_preference_name)}-theme`;
}
function switchStyle({ styles_element, css_title }) {
// Disable all other theme stylesheets
styles_element.querySelectorAll('link[rel*="stylesheet"][title*="theme"]')
.forEach(function (link) {
link.disabled = true;
});
// Enable the stylesheet with the specified title
const link = styles_element.querySelector(`link[rel*="stylesheet"][title="${css_title}"]`);
link && (link.disabled = false);
setPreference(PROPS.style_preference_name, css_title.replace(/-theme/, ''));
}
function setVisibility(component) {
component.style.visibility = 'visible';
}
export default function Theme({ component, style }) {
if (style == undefined) {
style = getPreferredTheme();
}
style && switchStyle({ styles_element: document, css_title: style });
// Check for the data attribute and set visibility if needed
if (component.dataset?.themeCallback === 'setVisibility') {
setVisibility(component);
}
}

View File

@ -1,50 +1,40 @@
// Store the host value for the current page
const currentPageHost = window.location.href.match(/^(?:[^\/]*\/){2}[^\/]+/g)[0];
// Define v3-wayfinding elements
var wayfindingModal = document.getElementById('v3-wayfinding-modal');
var wayfindingClose = document.getElementById('v3-wayfinding-close');
var wayfindingStay = document.getElementById('v3-wayfinding-stay');
var wayfindingSwitch = document.getElementById('v3-wayfinding-switch');
var wayfindingOptOut = document.getElementById('v3-wayfinding-opt-out');
var wayfindingOptOutInput = document.getElementById(
'v3-wayfinding-opt-out-input'
);
var wayfindingFindOutToggle = document.getElementById('find-out-toggle');
var wayfindingFindOutInstructions = document.getElementById(
'find-out-instructions'
);
import { CLOUD_URLS } from './influxdb-url.js';
import * as localStorage from './local-storage.js';
import { context, host, hostname, path, protocol, referrer, referrerHost } from './page-context.js';
/**
* Builds a referrer whitelist array that includes the current page host and all
* values from the cloudUrls array defined in layouts/partials/footer/javascript.html
*/
var referrerWhitelist = cloudUrls.concat(currentPageHost);
const cloudUrls = CLOUD_URLS || [];
var referrerWhitelist = cloudUrls.concat(host);
// v3-wayfinding preference cookie name
var wayfindingPrefCookie = 'v3_wayfinding_show';
// Toggle the v3-wayfinding modal
function toggleWayfinding () {
function toggleWayfinding() {
// Define v3-wayfinding elements
var wayfindingModal = document.getElementById('v3-wayfinding-modal');
wayfindingModal.classList.toggle('open');
}
// Toggle wayfinding modal preference cookie
function toggleWayfindingPreference () {
if (getPreference(wayfindingPrefCookie) === true) {
setPreference(wayfindingPrefCookie, false);
function toggleWayfindingPreference() {
if (localStorage.getPreference(wayfindingPrefCookie) === true) {
localStorage.setPreference(wayfindingPrefCookie, false);
} else {
setPreference(wayfindingPrefCookie, true);
localStorage.setPreference(wayfindingPrefCookie, true);
}
}
// Define the slideDown and slideUp animations
function slideDown (elem) {
function slideDown(elem) {
elem.style.height = `${elem.scrollHeight}px`;
elem.style.opacity = 1;
}
function slideUp (elem) {
function slideUp(elem) {
elem.style.height = 0;
elem.style.opacity = 0;
}
@ -54,41 +44,38 @@ function slideUp (elem) {
* - Is the user coming from a non-whitelisted external referrer?
* - Has the user opted out of the wayfinding modal?
*/
function shouldOpenWayfinding () {
function shouldOpenWayfinding() {
// Extract the protocol and hostname of referrer
const referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g);
const referrerHost = referrerMatch ? referrerMatch[0] : '';
var isExternalReferrer = !referrerWhitelist.includes(referrerHost);
var wayfindingOptedOut = getPreference(wayfindingPrefCookie);
const isExternalReferrer = !referrerWhitelist.includes(referrerHost);
const preferToShow = localStorage.getPreference(wayfindingPrefCookie);
// Only return true if all conditions are true
return isExternalReferrer && wayfindingOptedOut;
return isExternalReferrer && preferToShow;
}
/**
* Function that checks the wayfindingPrefCookie and sets the state of the
* wayfinding checkbox input.
*/
function setWayfindingInputState () {
var currentPreference = getPreference(wayfindingPrefCookie);
function setWayfindingInputState() {
const preferToShow = localStorage.getPreference(wayfindingPrefCookie);
const wayfindingOptOutInput = document.getElementById(
'v3-wayfinding-opt-out-input'
);
if (currentPreference === false) {
if (preferToShow === false) {
wayfindingOptOutInput.checked = true;
}
}
function submitWayfindingData (engine, action) {
const pageData = {
host: location.hostname,
path: location.pathname,
referrer: document.referrer === '' ? 'direct' : document.referrer,
};
function submitWayfindingData(engine, action) {
// Build lp using page data and engine data
const lp = `ioxwayfinding,host=${pageData.host},path=${pageData.path},referrer=${pageData.referrer},engine=${engine} action="${action}"`;
const lp = `ioxwayfinding,host=${hostname},path=${path},referrer=${referrer},engine=${engine} action="${action}"`;
// Send the wayfinding data
xhr = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
xhr.open(
'POST',
'https://j32dswat7l.execute-api.us-east-1.amazonaws.com/prod/wayfinding'
@ -96,7 +83,7 @@ function submitWayfindingData (engine, action) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader(
'Access-Control-Allow-Origin',
`${location.protocol}//${location.host}`
`${protocol}//${host}`
);
xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
xhr.setRequestHeader('Accept', 'application/json');
@ -105,36 +92,50 @@ function submitWayfindingData (engine, action) {
return false;
}
// When the user clicks on the stay button, close modal, submit data, and stay on the page.
wayfindingStay.onclick = function (event) {
function initialize() {
const wayfindingVersions = ['serverless', 'cloud'];
if (!wayfindingVersions.includes(context)) {
return;
}
// When the user clicks on the stay button, close modal, submit data, and stay on the page.
var wayfindingStay = document.getElementById('v3-wayfinding-stay');
wayfindingStay.onclick = function (event) {
var engine = wayfindingStay.dataset.engine;
var action = 'stay';
event.preventDefault();
submitWayfindingData(engine, action);
toggleWayfinding();
};
};
// When the user clicks on the switch button, submit data and follow link.
wayfindingSwitch.onclick = function (event) {
// When the user clicks on the switch button, submit data and follow link.
var wayfindingSwitch = document.getElementById('v3-wayfinding-switch');
wayfindingSwitch.onclick = function () {
var engine = wayfindingSwitch.dataset.engine;
var action = 'switch';
submitWayfindingData(engine, action);
};
};
// When the user clicks on the "X" wayfinding close element, close the modal
wayfindingClose.onclick = function (event) {
// When the user clicks on the "X" wayfinding close element, close the modal
var wayfindingClose = document.getElementById('v3-wayfinding-close');
wayfindingClose.onclick = function () {
toggleWayfinding();
};
};
wayfindingOptOut.onclick = function (event) {
var wayfindingOptOut = document.getElementById('v3-wayfinding-opt-out');
wayfindingOptOut.onclick = function () {
toggleWayfindingPreference();
};
};
// Toggle instructions for finding out which storage engine you're using
wayfindingFindOutToggle.onclick = function (event) {
// Toggle instructions for finding out which storage engine you're using
var wayfindingFindOutToggle = document.getElementById('find-out-toggle');
wayfindingFindOutToggle.onclick = function (event) {
event.preventDefault();
var wayfindingFindOutInstructions = document.getElementById(
'find-out-instructions'
);
if (wayfindingFindOutInstructions.classList.contains('open')) {
slideUp(wayfindingFindOutInstructions);
wayfindingFindOutInstructions.classList.remove('open');
@ -142,16 +143,18 @@ wayfindingFindOutToggle.onclick = function (event) {
slideDown(wayfindingFindOutInstructions);
wayfindingFindOutInstructions.classList.add('open');
}
};
};
/**
// Set the state of the show wayfinding input checkbox
setWayfindingInputState();
/**
* Check to see if the referrer is in the referrer whitelist, otherwise trigger
* the v3-wayfinding modal.
* This reuses the referrerHost variable defined in assets/js/influxdb-url.js
*/
if (shouldOpenWayfinding()) {
if (shouldOpenWayfinding()) {
toggleWayfinding();
}
}
// Set the state of the show wayfinding input checkbox
setWayfindingInputState();
export { initialize };

10
assets/jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*"
]
}
}
}

View File

@ -1,65 +0,0 @@
.custom-time-trigger {
display: block;
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
border-radius: $radius;
box-shadow: 2px 2px 6px $sidebar-search-shadow;
z-index: 1;
color: $g20-white;
background: $g5-pepper;
&:before {
content: "";
position: absolute;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
border-radius: $radius;
@include gradient($article-btn-gradient, 320deg);
z-index: -1;
opacity: 0;
transition: opacity .2s;
}
&:hover:before {opacity: 1}
a {
display: block;
padding: 1rem;
line-height: 1rem;
&:before {
content: "Select custom date for sample data";
display: inline-block;
overflow: hidden;
font-size: .9rem;
font-style: italic;
white-space: nowrap;
width: 0;
opacity: 0;
transition: width .2s, opacity .2s;
}
&:hover {
cursor: pointer;
&:before{
width: 240px;
opacity: 1;
}
}
}
}
///////////////////////////////// MEDIA QUERIES ////////////////////////////////
@include media(small) {
.custom-time-trigger {
bottom: .75rem;
right: .75rem;
}
}

View File

@ -0,0 +1,139 @@
.footer-widgets {
position: fixed;
bottom: 1rem;
right: 1rem;
width: auto; /* Ensure the parent takes up the full width */
height: auto; /* Adjust height to fit content */
display: flex;
flex-direction: column; /* Arrange items in a single column */
justify-content: flex-end; /* Align items to the start of the column */
align-items: flex-end; /* Align items to the left */
z-index: 100; /* Ensure the triggers are above other content except modals*/
.widget {
height: 50px;
width: 50px;
border-radius: $radius * 3;
position: relative;
box-shadow: 2px 2px 6px $sidebar-search-shadow;
color: $g20-white;
&:not(:last-child) {margin-bottom: 5px;}
/* The before pseudo element contains the "tool tip" */
&:before {
content: attr(data-tooltip);
padding: .25rem .5rem;
display: flex;
position: absolute;
top: 11px;
right: 60px;
width: auto;
white-space: nowrap;
font-size: .9rem;
font-weight: bold;
border-radius: $radius * 3;
@include gradient($grad-burningDusk, 270deg);
pointer-events: none;
z-index: -1;
opacity: 0;
transform: translateX(15px);
transition: opacity .2s, transform .2s;
}
&:after {
content: "";
position: absolute;
top: 14px;
right: 56px;
width: 0px;
height: 0px;
opacity: 0;
transform: translateX(15px);
transition: opacity .2s, transform .2s;
}
&:hover {
cursor: pointer;
&:before, &:after {
opacity: 1;
transform: translateX(0);
}
}
&.magenta {
@include gradient($grad-burningDusk, 90deg);
&:before {@include gradient($grad-burningDusk, 270deg);}
&:after {
border-style: solid;
border-width: 10px 0 10px 5px;
border-color: transparent transparent transparent $br-new-magenta;}
}
&.blue {
color: rgba($br-dark-blue, .7);
@include gradient($grad-tealDream, 270deg);
&:before {@include gradient($grad-tealDream, 90deg);}
&:after {
border-style: solid;
border-width: 10px 0 10px 5px;
border-color: transparent transparent transparent $br-teal;}
}
}
}
////////////////////////////// CUSTOM TIME TRIGGER /////////////////////////////
.custom-time-trigger {
a {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
font-size: 1.2rem;
color: rgba($br-dark-blue, .8);
text-decoration: none;
}
}
///////////////////////////////// ASK AI WIDGET ////////////////////////////////
.ask-ai-trigger {
.ask-ai-open {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
text-align: center;
align-items: center;
flex-direction: column;
font-size: .8rem;
font-weight: bold;
.icon-influx-logo {
margin-bottom: .15rem;
font-size: 1rem;
}
&:hover {
cursor: pointer;
}
}
}
////////////////////////////////// ANIMATIONS //////////////////////////////////
@keyframes fadeInAnimation {
0% {opacity: 0;}
100% {opacity: 1;}
}
///////////////////////////////// MEDIA QUERIES ////////////////////////////////
@include media(small) {
.footer-widgets {
bottom: .75rem;
right: .75rem;
}
}

View File

@ -187,7 +187,7 @@
margin-left: .5rem;
}
.theme-switcher, #search-btn, .url-trigger {
.theme-switch, #search-btn, .url-trigger {
display: inline-block;
padding: 0;
line-height: 0;
@ -206,8 +206,8 @@
color: rgba($topnav-link, 1);
cursor: pointer;
}
&#theme-switch-dark { display: $theme-switch-dark; font-size: 1.15rem; }
&#theme-switch-light { display: $theme-switch-light; font-size: 1.3rem; }
&.theme-switch-dark { display: $theme-switch-dark; font-size: 1.15rem; }
&.theme-switch-light { display: $theme-switch-light; font-size: 1.3rem; }
}
.url-trigger {

View File

@ -24,12 +24,12 @@
"layouts/algolia-search-overrides",
"layouts/landing",
"layouts/error-page",
"layouts/footer-widgets",
"layouts/modals",
"layouts/loading-spinner",
"layouts/feature-callouts",
"layouts/v1-overrides",
"layouts/notifications",
"layouts/custom-time-trigger",
"layouts/code-controls",
"layouts/v3-wayfinding";

View File

@ -268,7 +268,16 @@ With `accept_partial=true`:
< date: Wed, 15 Jan 2025 19:35:36 GMT
<
* Connection #0 to host localhost left intact
{"error":"partial write of line protocol occurred","data":[{"original_line":"dquote> home,room=Sunroom temp=hi","line_number":2,"error_message":"No fields were provided"}]}%
{
"error": "partial write of line protocol occurred",
"data": [
{
"original_line": "dquote> home,room=Sunroom temp=hi",
"line_number": 2,
"error_message": "No fields were provided"
}
]
}
```
Line `1` is written and queryable.
@ -278,16 +287,28 @@ The response is an HTTP error (`400`) status, and the response body contains `pa
With `accept_partial=false`:
```
> curl -v -XPOST "localhost:8181/api/v3/write_lp?db=sensors&precision=auto&accept_partial=false" \
```bash
curl -v "http://{{< influxdb/host >}}/api/v3/write_lp?db=sensors&precision=auto&accept_partial=false" \
--data-raw "home,room=Sunroom temp=96
dquote> home,room=Sunroom temp=hi"
home,room=Sunroom temp=hi"
```
The response is the following:
```
< HTTP/1.1 400 Bad Request
< transfer-encoding: chunked
< date: Wed, 15 Jan 2025 19:28:27 GMT
<
* Connection #0 to host localhost left intact
{"error":"parsing failed for write_lp endpoint","data":{"original_line":"dquote> home,room=Sunroom temp=hi","line_number":2,"error_message":"No fields were provided"}}%
{
"error": "parsing failed for write_lp endpoint",
"data": {
"original_line": "home,room=Sunroom temp=hi",
"line_number": 2,
"error_message": "No fields were provided"
}
}
```
Neither line is written to the database.
@ -401,7 +422,7 @@ Use the `format` parameter to specify the response format: `pretty`, `jsonl`, `p
The following example sends an HTTP `GET` request with a URL-encoded SQL query:
```bash
curl -v "http://127.0.0.1:8181/api/v3/query_sql?db=servers&q=select+*+from+cpu+limit+5"
curl -v "http://{{< influxdb/host >}}/api/v3/query_sql?db=servers&q=select+*+from+cpu+limit+5"
```
##### Example: Query passing JSON parameters
@ -409,7 +430,7 @@ curl -v "http://127.0.0.1:8181/api/v3/query_sql?db=servers&q=select+*+from+cpu+l
The following example sends an HTTP `POST` request with parameters in a JSON payload:
```bash
curl http://127.0.0.1:8181/api/v3/query_sql \
curl http://{{< influxdb/host >}}/api/v3/query_sql \
--data '{"db": "server", "q": "select * from cpu limit 5"}'
```
@ -430,7 +451,7 @@ From here, you can connect to your database with the client library using just t
from influxdb_client_3 import InfluxDBClient3
client = InfluxDBClient3(
host='http://127.0.0.1:8181',
host='http://{{< influxdb/host >}}',
database='servers'
)
```
@ -442,7 +463,7 @@ use PyArrow to explore the schema and process results:
from influxdb_client_3 import InfluxDBClient3
client = InfluxDBClient3(
host='http://127.0.0.1:8181',
host='http://{{< influxdb/host >}}',
database='servers'
)

View File

@ -263,7 +263,16 @@ With `accept_partial=true`:
< date: Wed, 15 Jan 2025 19:35:36 GMT
<
* Connection #0 to host localhost left intact
{"error":"partial write of line protocol occurred","data":[{"original_line":"dquote> home,room=Sunroom temp=hi","line_number":2,"error_message":"No fields were provided"}]}%
{
"error": "partial write of line protocol occurred",
"data": [
{
"original_line": "dquote> home,room=Sunroom temp=hi",
"line_number": 2,
"error_message": "No fields were provided"
}
]
}
```
Line `1` is written and queryable.
@ -273,16 +282,28 @@ The response is an HTTP error (`400`) status, and the response body contains `pa
With `accept_partial=false`:
```
> curl -v -XPOST "localhost:8181/api/v3/write_lp?db=sensors&precision=auto&accept_partial=false" \
```bash
curl -v "http://{{< influxdb/host >}}/api/v3/write_lp?db=sensors&precision=auto&accept_partial=false" \
--data-raw "home,room=Sunroom temp=96
dquote> home,room=Sunroom temp=hi"
home,room=Sunroom temp=hi"
```
The response is the following:
```
< HTTP/1.1 400 Bad Request
< transfer-encoding: chunked
< date: Wed, 15 Jan 2025 19:28:27 GMT
<
* Connection #0 to host localhost left intact
{"error":"parsing failed for write_lp endpoint","data":{"original_line":"dquote> home,room=Sunroom temp=hi","line_number":2,"error_message":"No fields were provided"}}%
{
"error": "parsing failed for write_lp endpoint",
"data": {
"original_line": "home,room=Sunroom temp=hi",
"line_number": 2,
"error_message": "No fields were provided"
}
}
```
Neither line is written to the database.
@ -394,7 +415,7 @@ Use the `format` parameter to specify the response format: `pretty`, `jsonl`, `p
The following example sends an HTTP `GET` request with a URL-encoded SQL query:
```bash
curl -v "http://127.0.0.1:8181/api/v3/query_sql?db=servers&q=select+*+from+cpu+limit+5"
curl -v "http://{{< influxdb/host >}}/api/v3/query_sql?db=servers&q=select+*+from+cpu+limit+5"
```
##### Example: Query passing JSON parameters
@ -402,7 +423,7 @@ curl -v "http://127.0.0.1:8181/api/v3/query_sql?db=servers&q=select+*+from+cpu+l
The following example sends an HTTP `POST` request with parameters in a JSON payload:
```bash
curl http://127.0.0.1:8181/api/v3/query_sql \
curl http://{{< influxdb/host >}}/api/v3/query_sql \
--data '{"db": "server", "q": "select * from cpu limit 5"}'
```
@ -423,7 +444,7 @@ From here, you can connect to your database with the client library using just t
from influxdb_client_3 import InfluxDBClient3
client = InfluxDBClient3(
host='http://127.0.0.1:8181',
host='http://{{< influxdb/host >}}',
database='servers'
)
```
@ -435,7 +456,7 @@ use PyArrow to explore the schema and process results:
from influxdb_client_3 import InfluxDBClient3
client = InfluxDBClient3(
host='http://127.0.0.1:8181',
host='http://{{< influxdb/host >}}',
database='servers'
)
@ -659,8 +680,7 @@ def process_writes(influxdb3_local, table_batches, args=None):
##### Test a plugin on the server
Use InfluxDB 3 to safely test a plugin before you load it, without touching written data.
During a plugin test:
Test your InfluxDB 3 plugin safely without affecting written data. During a plugin test:
- A query executed by the plugin queries against the server you send the request to.
- Writes aren't sent to the server but are returned to you.
@ -795,7 +815,7 @@ The example commands pass the following options:
# compactor-id: 'c01'
influxdb3 serve --node-id=host01 --read-from-node-ids=host02 --compactor-id=c01 --run-compactions --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8181 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host01 --read-from-node-ids=host02 --compactor-id=c01 --run-compactions --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://{{< influxdb/host >}} --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
```
@ -805,7 +825,7 @@ influxdb3 serve --node-id=host01 --read-from-node-ids=host02 --compactor-id=c01
# node-id: 'host02'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host02 --read-from-node-ids=host01 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8282
influxdb3 serve --node-id=host02 --read-from-node-ids=host01 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://localhost:8282
--aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
@ -821,12 +841,12 @@ influxdb3 serve --read-from-node-ids=host01,host02 [...OPTIONS]
>
> ```bash
> # In terminal 1
> influxdb3 serve --node-id=host01 --http-bind=http://127.0.0.1:8181 [...OPTIONS]
> influxdb3 serve --node-id=host01 --http-bind=http://{{< influxdb/host >}} [...OPTIONS]
> ```
>
> ```bash
> # In terminal 2
> influxdb3 serve --node-id=host01 --http-bind=http://127.0.0.1:8181 [...OPTIONS]
> influxdb3 serve --node-id=host01 --http-bind=http://{{< influxdb/host >}} [...OPTIONS]
### High availability with a dedicated Compactor
@ -847,7 +867,7 @@ The following examples show how to set up HA with a dedicated Compactor node:
# node-id: 'host01'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host01 --compactor-id=c01 --read-from-node-ids=host02 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8181 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host01 --compactor-id=c01 --read-from-node-ids=host02 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://{{< influxdb/host >}} --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
```bash
@ -857,7 +877,7 @@ The following examples show how to set up HA with a dedicated Compactor node:
# node-id: 'host02'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host02 --compactor-id=c01 --read-from-node-ids=host01 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8282 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host02 --compactor-id=c01 --read-from-node-ids=host01 --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://localhost:8282 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
2. Start the dedicated compactor node, which uses the following options:
@ -895,10 +915,12 @@ For a very robust and effective setup for managing time-series data, you can run
# node-id: 'host01'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host01 --mode=read_write --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8181 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host01 --mode=read_write --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://{{< influxdb/host >}} --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
<!-- The following examples use different ports for different nodes. Don't use the influxdb/host shortcode below. -->
```
## NODE 2 — Writer Node #2
@ -906,7 +928,7 @@ For a very robust and effective setup for managing time-series data, you can run
# node-id: 'host02'
# bucket: 'influxdb-3-enterprise-storage'
Usage: $ influxdb3 serve --node-id=host02 --mode=read_write --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8282 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
Usage: $ influxdb3 serve --node-id=host02 --mode=read_write --object-store=s3 --bucket=influxdb-3-enterprise-storage --http-bind=http://localhost:8282 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
2. Start the dedicated Compactor node (`--mode=compactor`) and ensure it runs compactions on the specified `compaction-hosts`.
@ -934,7 +956,7 @@ For a very robust and effective setup for managing time-series data, you can run
# node-id: 'host04'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host04 --mode=read --object-store=s3 --read-from-node-ids=host01,host02 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8383 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host04 --mode=read --object-store=s3 --read-from-node-ids=host01,host02 --bucket=influxdb-3-enterprise-storage --http-bind=http://localhost:8383 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
```
@ -944,7 +966,7 @@ For a very robust and effective setup for managing time-series data, you can run
# node-id: 'host05'
# bucket: 'influxdb-3-enterprise-storage'
influxdb3 serve --node-id=host05 --mode=read --object-store=s3 --read-from-node-ids=host01,host02 --bucket=influxdb-3-enterprise-storage --http-bind=0.0.0.0:8484 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
influxdb3 serve --node-id=host05 --mode=read --object-store=s3 --read-from-node-ids=host01,host02 --bucket=influxdb-3-enterprise-storage --http-bind=http://localhost:8484 --aws-access-key-id=<AWS_ACCESS_KEY_ID> --aws-secret-access-key=<AWS_SECRET_ACCESS_KEY>
```
Congratulations, you have a robust setup to workload isolation using {{% product-name %}}.
@ -968,7 +990,7 @@ You can use the default port `8181` for any write or query, without changing any
```
# Example variables on a query
# HTTP-bound Port: 8585
Usage: $ influxdb3 query --host=http://127.0.0.1:8585 -d <DATABASE> "<QUERY>"
Usage: $ influxdb3 query --host=http://localhost:8585 -d <DATABASE> "<QUERY>"
```
### File index settings
@ -982,11 +1004,11 @@ This feature is only available in Enterprise and is not available in Core.
# Example variables on a query
# HTTP-bound Port: 8585
influxdb3 create file_index --host=http://127.0.0.1:8585 -d <DATABASE> -t <TABLE> <COLUMNS>
influxdb3 create file_index --host=http://localhost:8585 -d <DATABASE> -t <TABLE> <COLUMNS>
```
#### Delete a file index
```bash
influxdb3 delete file_index --host=http://127.0.0.1:8585 -d <DATABASE> -t <TABLE>
influxdb3 delete file_index --host=http://localhost:8585 -d <DATABASE> -t <TABLE>
```

View File

@ -66,3 +66,20 @@ cloud:
- name: East US (Virginia)
location: Virginia, USA
url: https://eastus-1.azure.cloud2.influxdata.com
cloud_dedicated:
providers:
- name: Default
regions:
- name: cluster-id.a.influxdb.io
url: https://cluster-id.a.influxdb.io
- name: Custom
url: http://example.com:8080
product: InfluxDB Cloud Dedicated
clustered:
providers:
- name: Default
regions:
- name: cluster-host.com
url: https://cluster-host.com
- name: Custom
url: http://example.com:8080

View File

@ -7,6 +7,10 @@ influxdb3_core:
list_order: 2
latest: core
placeholder_host: localhost:8181
ai_sample_questions:
- How do I install and run InfluxDB 3 Core?
- How do I write a plugin for the Python Processing engine?
- How do I write data using the HTTP API for InfluxDB 3 Core?
influxdb3_enterprise:
name: InfluxDB 3 Enterprise
@ -17,6 +21,10 @@ influxdb3_enterprise:
list_order: 2
latest: enterprise
placeholder_host: localhost:8181
ai_sample_questions:
- How do I install and run InfluxDB 3 Enterprise?
- Help me write a plugin for the Python Processing engine?
- How do I start a read replica node with InfluxDB 3 Enterprise?
influxdb3_cloud_serverless:
name: InfluxDB Cloud Serverless
@ -27,6 +35,9 @@ influxdb3_cloud_serverless:
list_order: 2
latest: cloud-serverless
placeholder_host: cloud2.influxdata.com
ai_sample_questions:
- How do I migrate from InfluxDB Cloud 2 to InfluxDB Cloud Serverless?
- What tools can I use to write data to InfluxDB Cloud Serverless?
influxdb3_cloud_dedicated:
name: InfluxDB Cloud Dedicated
@ -39,6 +50,10 @@ influxdb3_cloud_dedicated:
link: "https://www.influxdata.com/contact-sales-form/"
latest_cli: 2.9.9
placeholder_host: cluster-id.a.influxdb.io
ai_sample_questions:
- How do I migrate from InfluxDB v1 to InfluxDB Cloud Dedicated?
- What tools can I use to write data to Cloud Dedicated?
- How do I use SQL and parameterized queries with Cloud Dedicated?
influxdb3_clustered:
name: InfluxDB Clustered
@ -50,6 +65,10 @@ influxdb3_clustered:
latest: clustered
link: "https://www.influxdata.com/contact-sales-influxdb-clustered"
placeholder_host: cluster-host.com
ai_sample_questions:
- How do I use a Helm chart to configure Clustered?
- What tools can I use to write data to Clustered?
- How do I use SQL and parameterized queries with InfluxDB Clustered?
influxdb:
name: InfluxDB
@ -68,6 +87,10 @@ influxdb:
v1: 1.11.8
latest_cli:
v2: 2.7.5
ai_sample_questions:
- How do I write and query data with InfluxDB v2 OSS?
- How can I migrate from InfluxDB v2 OSS to InfluxDB 3 Core?
- How do I manage auth tokens in InfluxDB v2 OSS?
influxdb_cloud:
name: InfluxDB Cloud (TSM)
@ -78,6 +101,10 @@ influxdb_cloud:
list_order: 1
latest: cloud
placeholder_host: cloud2.influxdata.com
ai_sample_questions:
- How do I write and query data with InfluxDB Cloud 2?
- How is Cloud 2 different from Cloud Serverless?
- How do I manage auth tokens in InfluxDB Cloud 2?
telegraf:
name: Telegraf
@ -88,6 +115,10 @@ telegraf:
latest: v1.33
latest_patches:
v1: 1.33.0
ai_sample_questions:
- How do I install and configure Telegraf?
- How do I write a custom Telegraf plugin?
- How do I use Telegraf for MQTT?
chronograf:
name: Chronograf
@ -118,6 +149,10 @@ enterprise_influxdb:
latest: v1.11
latest_patches:
v1: 1.11.8
ai_sample_questions:
- How can I configure my InfluxDB v1 Enterprise server?
- How do I replicate data between InfluxDB v1 Enterprise and OSS?
- How do I query data stored in InfluxDB v1?
flux:
name: Flux

9
eslint.config.mjs Normal file
View File

@ -0,0 +1,9 @@
import globals from "globals";
import pluginJs from "@eslint/js";
/** @type {import('eslint').Linter.Config[]} */
export default [
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
];

View File

@ -9,13 +9,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="shortcut icon" href="/img/favicon.png" type="image/png" sizes="32x32">
{{ partial "header/javascript.html" }}
{{ partial "header/stylesheets.html" }}
{{ partial "header/google-fonts.html" }}
{{ partial "header/javascript.html" }}
<meta name="Copyright" content="InfluxData Inc." />
</head>
<body>
<body data-component="theme" data-theme-callback="setVisibility">
{{ partial "header/google-analytics-body.html" }}
@ -67,4 +67,3 @@
</body>
{{ partial "footer/javascript.html" . }}
</html>

View File

@ -13,8 +13,8 @@
<!-- Docs Notifications -->
{{ partial "footer/notifications.html" . }}
<!-- Custom time modal trigger-->
{{ partial "footer/custom-time-trigger" . }}
<!-- Page widgets -->
{{ partial "footer/widgets.html" . }}
<!-- IOx wayfinding modal -->
{{ if in (slice "cloud" "cloud-serverless") $version }}

View File

@ -1,5 +0,0 @@
{{ if or (.Page.HasShortcode "influxdb/custom-timestamps") (.Page.HasShortcode "influxdb/custom-timestamps-span") }}
<div class="custom-time-trigger">
<a onclick='toggleModal("#influxdb-gs-date-select")'><span class="cf-icon Clock_New"></span></a>
</div>
{{ end }}

View File

@ -1,29 +1,18 @@
{{ $jquery := resources.Get "js/jquery-3.5.0.min.js" }}
{{ $versionSelector := resources.Get "js/version-selector.js" }}
{{ $contentInteractions := resources.Get "js/content-interactions.js" }}
{{ $searchInteractions := resources.Get "js/search-interactions.js" }}
{{ $listFilters := resources.Get "js/list-filters.js" }}
{{ $modals := resources.Get "js/modals.js" }}
{{ $influxdbURLs := resources.Get "js/influxdb-url.js" }}
{{ $featureCallouts := resources.Get "js/feature-callouts.js" }}
{{ $tabbedContent := resources.Get "js/tabbed-content.js" }}
{{ $apiLibs := resources.Get "js/api-libs.js" }}
{{ $notifications := resources.Get "js/notifications.js" }}
{{ $keybindings := resources.Get "js/keybindings.js" }}
{{ $fluxGroupKeys := resources.Get "js/flux-group-keys.js" }}
{{ $dateTime := resources.Get "js/datetime.js" }}
{{ $influxdbGSTimestamps := resources.Get "js/custom-timestamps.js" }}
{{ $codeControls := resources.Get "js/code-controls.js" }}
{{ $pageFeedback := resources.Get "js/page-feedback.js" }}
{{ $homepageInteractions := resources.Get "js/home-interactions.js" }}
{{ $fluxInfluxDBVersions := resources.Get "/js/flux-influxdb-versions.js" }}
{{ $v3Wayfinding := resources.Get "/js/v3-wayfinding.js"}}
{{ $codePlaceholders := resources.Get "/js/code-placeholders.js" }}
{{ $releaseTOC := resources.Get "/js/release-toc.js" }}
{{ $footerjs := slice $versionSelector $contentInteractions $searchInteractions $listFilters $modals $influxdbURLs $featureCallouts $tabbedContent $apiLibs $notifications $keybindings $codeControls $pageFeedback $homepageInteractions $fluxInfluxDBVersions | resources.Concat "js/footer.bundle.js" | resources.Fingerprint }}
{{ $footerjs := slice $versionSelector $searchInteractions $listFilters $featureCallouts $keybindings $homepageInteractions $fluxInfluxDBVersions | resources.Concat "js/footer.bundle.js" | resources.Fingerprint }}
{{ $fluxGroupKeyjs := $fluxGroupKeys | resources.Fingerprint }}
{{ $dateTimejs := $dateTime | resources.Fingerprint }}
{{ $influxdbGSTimestampsjs := $influxdbGSTimestamps | resources.Fingerprint }}
{{ $v3Wayfindingjs := $v3Wayfinding | resources.Fingerprint }}
{{ $codePlaceholdersjs := $codePlaceholders | resources.Fingerprint }}
{{ $releaseTOCjs := $releaseTOC | resources.Fingerprint }}
@ -62,17 +51,6 @@
<script type="text/javascript" src="{{ $dateTimejs.RelPermalink }}"></script>
{{ end }}
<!-- Load getting started timestamps js if when the influxdb/custom-gs-timestamps shortcode is present -->
{{ if or (.Page.HasShortcode "influxdb/custom-timestamps") (.Page.HasShortcode "influxdb/custom-timestamps-span") }}
<script src="https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.2.0/dist/js/datepicker.min.js"></script>
<script type="text/javascript" src="{{ $influxdbGSTimestampsjs.RelPermalink }}"></script>
{{ end }}
<!-- Load IOx wayfinding JS if page is in cloud or cloud-serverless -->
{{ if in (slice "cloud" "cloud-serverless") (index (findRE "[^/]+.*?" .Page.RelPermalink) 1) }}
<script type="text/javascript" src="{{ $v3Wayfinding.RelPermalink }}"></script>
{{ end }}
<!-- Load code placeholders js when code-placeholders shortcode is present -->
{{ if .Page.HasShortcode "code-placeholders" }}
<script type="text/javascript" src="{{ $codePlaceholdersjs.RelPermalink }}"></script>

View File

@ -8,7 +8,7 @@
{{ partial "footer/modals/influxdb-url.html" . }}
{{ partial "footer/modals/page-feedback.html" . }}
{{ if or (.Page.HasShortcode "influxdb/custom-timestamps") (.Page.HasShortcode "influxdb/custom-timestamps-span") }}
{{ partial "footer/modals/influxdb-gs-date-select.html" }}
{{ partial "footer/modals/influxdb-gs-date-select.html" . }}
{{ end }}
{{ if $inStdlib }}
{{ partial "footer/modals/flux-influxdb-versions.html" . }}

View File

@ -1,6 +1,11 @@
{{ $productPathData := findRE "[^/]+.*?" $.Page.RelPermalink }}
{{ $version := index $productPathData 1 }}
{{ $dbWhiteList := (slice "core" "enterprise" "cloud-dedicated" "clustered") }}
{{ $useDB := in $dbWhiteList $version }}
{{ $storageTerm := cond $useDB "database" "bucket" }}
<div class="modal-content" id="influxdb-gs-date-select">
<h3>Select a new date</h3>
<p><em>Select a date in your bucket's retention period.</em></p>
<p><em>Select a date in your {{ $storageTerm }}'s retention period.</em></p>
<div id="custom-date-selector"></div>
<a class="btn" id="submit-custom-date" onclick="">Update</a>
</div>

View File

@ -0,0 +1,6 @@
<div class="footer-widgets">
<!-- Custom time modal trigger -->
{{ partial "footer/widgets/custom-time-trigger" . }}
<!-- Ask AI modal trigger -->
{{ partial "footer/widgets/ask-ai-trigger" . }}
</div>

View File

@ -0,0 +1,6 @@
<div class="ask-ai-trigger widget magenta" data-component="ask-ai-trigger" data-tooltip="Ask InfluxData AI for help" style="display: none;">
<a class="ask-ai-open" >
<div class="icon-influx-logo"></div>
Ask AI
</a>
</div>

View File

@ -0,0 +1,7 @@
{{ if or (.Page.HasShortcode "influxdb/custom-timestamps") (.Page.HasShortcode "influxdb/custom-timestamps-span") }}
<div data-component="custom-time-trigger" class="custom-time-trigger widget blue" data-tooltip="Select custom date for sample data">
<a href="#" class="custom-time-trigger__button" data-action="open">
<span class="cf-icon Clock_New"></span>
</a>
</div>
{{ end }}

View File

@ -15,15 +15,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="shortcut icon" href="/img/favicon.png" type="image/png" sizes="32x32">
{{ partial "header/javascript.html" . }}
{{ partial "header/canonical.html" . }}
{{ partial "header/stylesheets.html" }}
{{ partial "header/google-fonts.html" }}
{{ partial "header/javascript.html" }}
{{ partial "header/search-attributes.html" . }}
{{ partial "header/coveo-meta-data.html" . }}
{{ if not hugo.IsServer }}{{ partial "header/marketing.html" }}{{ end }}
<meta name="Copyright" content="InfluxData Inc." />
</head>
<body class='{{if ne $product nil}}{{ $product }}{{ else }}home{{ end }}{{ if in $version "v1" }} v1{{ end }}'>
<body class='{{if ne $product nil}}{{ $product }}{{ else }}home{{ end }}{{ if in $version "v1" }} v1{{ end }}' data-component="theme" data-theme-callback="setVisibility">
{{ if not hugo.IsServer }}{{ partial "header/google-analytics-body.html" }}{{ end }}

View File

@ -1,9 +1,42 @@
<!-- START COMPONENT AND JS BUNDLING REFACTOR
Eventually, all site-specific JavaScript and external JS
dependencies will be bundled in main.js
-->
<!-- Legacy: keep jquery here until component refactor is for scripts in footer.bundle.js that still require it. -->
{{ $jquery := resources.Get "js/jquery-3.5.0.min.js" }}
{{ $localStorage := resources.Get "js/local-storage.js" }}
{{ $themes := resources.Get "js/docs-themes.js" }}
{{ $sidebar := resources.Get "js/sidebar-toggle.js" }}
{{ $headerjs := slice $jquery $localStorage $themes $sidebar | resources.Concat "js/header.bundle.js" | resources.Fingerprint }}
{{ $headerjs := slice $jquery | resources.Concat "js/header.bundle.js" | resources.Fingerprint }}
<script type="text/javascript" src="{{ $headerjs.RelPermalink }}"></script>
<script type="text/javascript">setStyleFromCookie();</script>
<script type="text/javascript">setSidebarState();</script>
<!-- $productPathData here is buggy - it might not return the current page path due to the context in which .RelPermalink is called -->
{{ $productPathData := findRE "[^/]+.*?" .RelPermalink }}
{{ $product := index $productPathData 0 }}
{{ $currentVersion := index $productPathData 1 }}
<!-- Get site data -->
<!-- Load cloudUrls array -->
{{ $cloudUrls := slice }}
{{- range.Site.Data.influxdb_urls.cloud.providers }}
{{- range.regions }}
{{ $cloudUrls = $cloudUrls | append "{{ safeHTML .url }}" }}
{{ end -}}
{{ end -}}
{{ $products := .Site.Data.products }}
{{ $influxdb_urls := .Site.Data.influxdb_urls }}
<!-- Build main.js -->
{{ with resources.Get "js/main.js" }}
{{ $opts := dict
"minify" hugo.IsProduction
"sourceMap" (cond hugo.IsProduction "" "external")
"targetPath" "js/main.js"
"params" (dict "product" $product "currentVersion" $currentVersion "isServer" hugo.IsServer "products" $products "influxdb_urls" $influxdb_urls "cloudUrls" $cloudUrls)
}}
{{ with . | js.Build $opts }}
{{ if hugo.IsProduction }}
{{ with . | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{ end }}
{{ else }}
<script src="{{ .RelPermalink }}"></script>
{{ end }}
{{ end }}
{{ end }}

View File

@ -1,3 +1,10 @@
<!-- Prevent flash of unstyled content -->
<style>
body {
visibility: hidden;
}
</style>
<!-- Docsearch Styles -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"/>

View File

@ -1,4 +1,4 @@
{{- $initialState := .state | default "Open" -}}
{{- $modifiedState := cond (eq $initialState "Close") "closed" "open" -}}
<div class="sidebar-toggle" onclick="toggleSidebar('sidebar-{{ $modifiedState }}');return false;"><a href="#"><span
<div class="sidebar-toggle" data-component="sidebar-toggle" data-state="{{ $modifiedState }}" ><a data-action="toggle" href="#"><span
class="cf-icon Sidebar{{ $initialState }}"></span></a></div>

View File

@ -14,16 +14,16 @@
{{ partial "topnav/product-selector.html" . }}
</div>
<div class="buttons">
<div class="search-btn">
<button id="search-btn"
onclick="toggleSidebar('sidebar-open');document.getElementById('algolia-search-input').focus();return false;"><span
class="cf-icon Search_New"></span></button>
<div class="search-btn" data-component="search-button">
<button id="search-btn" data-action="toggle"><span class="cf-icon Search_New"></span></button>
</div>
<button class="url-trigger" href="#"><span class="cf-icon CogSolid_New"></span></button>
<button class="theme-switcher" id="theme-switch-light" onclick="switchStyle('light-theme');return false;"><span
class="cf-icon Lightmode_New"></span></button>
<button class="theme-switcher" id="theme-switch-dark" onclick="switchStyle('dark-theme');return false;"><span
class="cf-icon Darkmode_New"></span></button>
<span data-component="theme-switch">
<button class="theme-switch theme-switch-light">
<span class="cf-icon Lightmode_New"></span></button>
<button class="theme-switch theme-switch-dark">
<span class="cf-icon Darkmode_New"></span></button>
</span>
</div>
</div>
</div>

View File

@ -5,18 +5,26 @@
"description": "InfluxDB documentation",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.18.0",
"@evilmartians/lefthook": "^1.7.1",
"@vvago/vale": "^3.4.2",
"autoprefixer": ">=10.2.5",
"eslint": "^9.18.0",
"globals": "^15.14.0",
"hugo-extended": ">=0.101.0",
"postcss": ">=8.4.31",
"postcss-cli": ">=9.1.0",
"prettier": "^3.2.5",
"prettier-plugin-sql": "^0.18.0"
"prettier-plugin-sql": "^0.18.0",
"winston": "^3.16.0"
},
"dependencies": {
"axios": "^1.7.4",
"js-yaml": "^4.1.0"
"jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"js-yaml": "^4.1.0",
"mermaid": "^11.4.1",
"vanillajs-datepicker": "^1.3.4"
},
"scripts": {
"lint": "LEFTHOOK_EXCLUDE=test lefthook run pre-commit && lefthook run pre-push",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

1597
yarn.lock

File diff suppressed because it is too large Load Diff