Merge branch 'feature-1.1.5-hardening'

This commit is contained in:
qf3l3k 2026-05-02 12:17:41 +02:00
commit 7442d1d615
8 changed files with 121 additions and 11 deletions

View file

@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file.
## [1.1.5] - 2026-05-02
### Added
- Added endpoint list filtering in settings by alias, type, or URL.
- Added an export confirmation dialog with an explicit `Include headers` option for endpoint JSON exports.
### Changed
- Endpoint exports now omit headers by default to avoid accidentally sharing API keys or tokens.
- Frontmatter output is no longer applied from cached render results; it still writes after fresh fetches and manual refreshes.
## [1.1.4] - 2026-03-21
### Added

View file

@ -18,6 +18,7 @@ Supported endpoint types:
- Endpoint alias configuration in plugin settings
- Compact endpoint list with modal editor (v1.1.1)
- Endpoint import/export for moving aliases between devices (v1.1.2)
- Endpoint filtering in settings and safer header-free exports by default (v1.1.5)
- Cache with configurable expiration
- Cache browser modal (list, preview, delete individual entries)
- Optional ribbon icon shortcut for cache browser
@ -184,6 +185,7 @@ Available options:
- Cache duration (minutes)
- Endpoint aliases (compact list with Name/Type/URL + actions)
- Endpoint import/export (JSON)
- Endpoint filter by alias, type, or URL
- Per-alias headers
- Cache clearing
- Cache browser shortcut
@ -212,6 +214,7 @@ Available options:
Use command `Open cache browser` (or settings button) to:
- list cache entries with size/date
- filter entries by alias, key, type, or URL
- preview cached payloads
- delete individual entries
- clear all cache

103
main.ts
View file

@ -21,7 +21,6 @@ export default class DataFetcherPlugin extends Plugin {
const cachedResult = await this.cacheManager.getFromCache(query);
if (cachedResult) {
await this.applyOutputTargetSafely(query, cachedResult, ctx);
this.renderResult(cachedResult, el, query, ctx);
} else {
el.createEl('div', { text: 'Fetching data...', cls: 'data-fetcher-loading' });
@ -710,6 +709,7 @@ export default class DataFetcherPlugin extends Plugin {
class DataFetcherSettingTab extends PluginSettingTab {
plugin: DataFetcherPlugin;
private endpointFilter = '';
constructor(app: App, plugin: DataFetcherPlugin) {
super(app, plugin);
@ -788,7 +788,9 @@ class DataFetcherSettingTab extends PluginSettingTab {
.addButton(button => button
.setButtonText('Export endpoints')
.onClick(() => {
this.exportEndpoints();
new EndpointExportModal(this.app, this.plugin.settings.endpoints, includeHeaders => {
this.exportEndpoints(includeHeaders);
}).open();
}))
.addButton(button => button
.setButtonText('Import endpoints')
@ -801,6 +803,19 @@ class DataFetcherSettingTab extends PluginSettingTab {
.setName('Endpoint aliases')
.setHeading();
const filterEl = containerEl.createEl('input', {
cls: 'data-fetcher-endpoint-filter',
attr: {
type: 'text',
placeholder: 'Filter endpoints by alias, type, or URL...'
}
});
filterEl.value = this.endpointFilter;
filterEl.addEventListener('input', () => {
this.endpointFilter = filterEl.value;
this.renderEndpointTable(containerEl);
});
this.renderEndpointTable(containerEl);
new Setting(containerEl)
@ -856,6 +871,7 @@ class DataFetcherSettingTab extends PluginSettingTab {
}
private renderEndpointTable(containerEl: HTMLElement): void {
containerEl.querySelector('.data-fetcher-endpoint-table')?.remove();
const table = containerEl.createEl('div', { cls: 'data-fetcher-endpoint-table' });
const header = table.createEl('div', { cls: 'data-fetcher-endpoint-row data-fetcher-endpoint-row-header' });
header.createEl('div', { text: 'Name', cls: 'data-fetcher-endpoint-col data-fetcher-endpoint-col-alias' });
@ -870,7 +886,28 @@ class DataFetcherSettingTab extends PluginSettingTab {
return;
}
this.plugin.settings.endpoints.forEach((endpoint, index) => {
const normalizedFilter = this.endpointFilter.trim().toLowerCase();
const visibleEndpoints = this.plugin.settings.endpoints
.map((endpoint, index) => ({ endpoint, index }))
.filter(({ endpoint }) => {
if (!normalizedFilter) {
return true;
}
return [
endpoint.alias || '',
endpoint.type || '',
endpoint.url || ''
].join(' ').toLowerCase().includes(normalizedFilter);
});
if (visibleEndpoints.length === 0) {
const empty = table.createEl('div', { cls: 'data-fetcher-endpoint-empty' });
empty.setText('No endpoints match current filter.');
return;
}
visibleEndpoints.forEach(({ endpoint, index }) => {
const row = table.createEl('div', { cls: 'data-fetcher-endpoint-row' });
row.createEl('div', {
text: endpoint.alias?.trim() || '(unnamed)',
@ -928,11 +965,19 @@ class DataFetcherSettingTab extends PluginSettingTab {
});
}
private exportEndpoints(): void {
private endpointForExport(endpoint: EndpointConfig, includeHeaders: boolean): EndpointConfig {
return {
...endpoint,
headers: includeHeaders ? { ...(endpoint.headers || {}) } : {}
};
}
private exportEndpoints(includeHeaders: boolean): void {
const payload = {
version: 1,
exportedAt: new Date().toISOString(),
endpoints: this.plugin.settings.endpoints
includesHeaders: includeHeaders,
endpoints: this.plugin.settings.endpoints.map(endpoint => this.endpointForExport(endpoint, includeHeaders))
};
const json = JSON.stringify(payload, null, 2);
const blob = new Blob([json], { type: 'application/json' });
@ -945,7 +990,8 @@ class DataFetcherSettingTab extends PluginSettingTab {
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(url);
new Notice(`Exported ${this.plugin.settings.endpoints.length} endpoint(s)`);
const headerMode = includeHeaders ? 'with headers' : 'without headers';
new Notice(`Exported ${this.plugin.settings.endpoints.length} endpoint(s) ${headerMode}`);
}
private readFileAsText(file: File): Promise<string> {
@ -1169,6 +1215,51 @@ class EndpointImportModeModal extends Modal {
}
}
class EndpointExportModal extends Modal {
private endpoints: EndpointConfig[];
private onSubmit: (includeHeaders: boolean) => void;
private includeHeaders = false;
constructor(app: App, endpoints: EndpointConfig[], onSubmit: (includeHeaders: boolean) => void) {
super(app);
this.endpoints = endpoints;
this.onSubmit = onSubmit;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
new Setting(contentEl)
.setName('Export endpoints')
.setHeading();
contentEl.createEl('p', {
text: `Export ${this.endpoints.length} endpoint(s) to JSON. Headers may contain API keys or tokens.`
});
new Setting(contentEl)
.setName('Include headers')
.setDesc('Disabled by default to avoid exporting secrets such as Authorization headers.')
.addToggle(toggle => toggle
.setValue(this.includeHeaders)
.onChange(value => {
this.includeHeaders = value;
}));
const actions = contentEl.createEl('div', { cls: 'data-fetcher-endpoint-editor-actions' });
actions.createEl('button', { text: 'Cancel' }).addEventListener('click', () => this.close());
actions.createEl('button', { text: 'Export', cls: 'mod-cta' }).addEventListener('click', () => {
this.onSubmit(this.includeHeaders);
this.close();
});
}
onClose() {
this.contentEl.empty();
}
}
class EndpointEditorModal extends Modal {
private endpoint: EndpointConfig;
private existingAliases: Set<string>;

View file

@ -1,7 +1,7 @@
{
"id": "data-fetcher",
"name": "Data Fetcher",
"version": "1.1.4",
"version": "1.1.5",
"minAppVersion": "1.8.3",
"description": "Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes",
"author": "qf3l3k",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-api-fetcher",
"version": "1.1.4",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-api-fetcher",
"version": "1.1.4",
"version": "1.1.5",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-api-fetcher",
"version": "1.1.4",
"version": "1.1.5",
"description": "Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes",
"main": "main.js",
"scripts": {

View file

@ -147,6 +147,11 @@
font-size: var(--font-ui-smaller);
}
.data-fetcher-endpoint-filter {
width: 100%;
margin-bottom: 10px;
}
.data-fetcher-endpoint-row {
display: grid;
grid-template-columns: minmax(84px, 1fr) 72px minmax(140px, 2fr) 64px minmax(150px, 1.2fr);

View file

@ -8,5 +8,6 @@
"1.1.1": "1.8.3",
"1.1.2": "1.8.3",
"1.1.3": "1.8.3",
"1.1.4": "1.8.3"
"1.1.4": "1.8.3",
"1.1.5": "1.8.3"
}