mirror of
https://github.com/viktoruj/obsidian-cloud-kms.git
synced 2026-07-22 06:56:21 +00:00
fix: use CSS classes instead of inline styles, bump minAppVersion to 1.7.2 [skip ci]
This commit is contained in:
parent
8f2bdb32e5
commit
0d7eb23a1c
7 changed files with 162 additions and 167 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"id": "cloud-kms-encryption",
|
||||
"name": "Cloud KMS Encryption",
|
||||
"version": "0.1.1",
|
||||
"minAppVersion": "1.4.0",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Transparent encryption of secret blocks and binary files using AWS KMS. Zero plaintext on disk.",
|
||||
"author": "Viktar Mikalayeu",
|
||||
"isDesktopOnly": true
|
||||
|
|
|
|||
|
|
@ -96,15 +96,7 @@ export class EncryptedFileView extends ItemView {
|
|||
container.empty();
|
||||
|
||||
// Error banner
|
||||
const banner = container.createEl('div', {
|
||||
cls: 'encrypted-view-error-banner',
|
||||
});
|
||||
banner.style.backgroundColor = 'var(--background-modifier-error)';
|
||||
banner.style.color = 'var(--text-on-accent)';
|
||||
banner.style.padding = '12px 16px';
|
||||
banner.style.borderRadius = '4px';
|
||||
banner.style.marginBottom = '16px';
|
||||
banner.style.fontWeight = '600';
|
||||
const banner = container.createEl('div', { cls: 'ocke-encrypted-view-banner' });
|
||||
|
||||
const errorIcon = banner.createEl('span');
|
||||
errorIcon.textContent = '⚠ ';
|
||||
|
|
@ -113,36 +105,17 @@ export class EncryptedFileView extends ItemView {
|
|||
errorText.textContent = this.errorMessage || 'Decryption failed';
|
||||
|
||||
if (this.filePath) {
|
||||
const fileInfo = banner.createEl('div');
|
||||
fileInfo.style.marginTop = '4px';
|
||||
fileInfo.style.fontWeight = '400';
|
||||
fileInfo.style.fontSize = '0.9em';
|
||||
const fileInfo = banner.createEl('div', { cls: 'ocke-encrypted-view-banner-detail' });
|
||||
fileInfo.textContent = `File: ${this.filePath}`;
|
||||
}
|
||||
|
||||
// Raw content section
|
||||
const contentSection = container.createEl('div', {
|
||||
cls: 'encrypted-view-content',
|
||||
});
|
||||
const contentSection = container.createEl('div');
|
||||
|
||||
const heading = contentSection.createEl('h4');
|
||||
const heading = contentSection.createEl('h4', { cls: 'ocke-encrypted-view-heading' });
|
||||
heading.textContent = 'Raw On-Disk Content (Base64)';
|
||||
heading.style.marginBottom = '8px';
|
||||
|
||||
const codeBlock = contentSection.createEl('pre', {
|
||||
cls: 'encrypted-view-raw-content',
|
||||
});
|
||||
codeBlock.style.whiteSpace = 'pre-wrap';
|
||||
codeBlock.style.wordBreak = 'break-all';
|
||||
codeBlock.style.padding = '12px';
|
||||
codeBlock.style.backgroundColor = 'var(--background-secondary)';
|
||||
codeBlock.style.borderRadius = '4px';
|
||||
codeBlock.style.fontSize = '0.85em';
|
||||
codeBlock.style.fontFamily = 'var(--font-monospace)';
|
||||
codeBlock.style.maxHeight = '70vh';
|
||||
codeBlock.style.overflow = 'auto';
|
||||
codeBlock.style.userSelect = 'text';
|
||||
|
||||
const codeBlock = contentSection.createEl('pre', { cls: 'ocke-encrypted-view-code' });
|
||||
const code = codeBlock.createEl('code');
|
||||
code.textContent = this.rawContentBase64 || '(no content)';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +1,53 @@
|
|||
/**
|
||||
* Adds a 🔒 badge to encrypted files in the file explorer.
|
||||
*
|
||||
* Scans vault for files with OCKE magic bytes and marks them
|
||||
* with a CSS class that shows a lock icon via ::after pseudo-element.
|
||||
* CSS is in styles.css (class: ocke-encrypted-file).
|
||||
*/
|
||||
|
||||
import type { Plugin } from 'obsidian';
|
||||
|
||||
const ENCRYPTED_CLASS = 'ocke-encrypted-file';
|
||||
const STYLE_ID = 'ocke-encrypted-badge-style';
|
||||
|
||||
/**
|
||||
* Set of file paths known to be encrypted.
|
||||
*/
|
||||
const encryptedPaths = new Set<string>();
|
||||
|
||||
/**
|
||||
* Install the file explorer badge system.
|
||||
* Returns a cleanup function.
|
||||
*/
|
||||
export function installFileExplorerBadge(
|
||||
plugin: Plugin,
|
||||
originalReadBinary: (path: string) => Promise<ArrayBuffer>
|
||||
): () => void {
|
||||
// Inject CSS for the badge
|
||||
const style = document.createElement('style');
|
||||
style.id = STYLE_ID;
|
||||
style.textContent = `
|
||||
.nav-file.${ENCRYPTED_CLASS} .nav-file-title-content::after {
|
||||
content: ' 🔒';
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Scan vault on layout ready
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
await scanVaultForEncryptedFiles(plugin, originalReadBinary);
|
||||
applyBadges(plugin);
|
||||
applyBadges();
|
||||
});
|
||||
|
||||
// Re-apply badges when file explorer updates
|
||||
// Re-apply badges periodically
|
||||
const interval = window.setInterval(() => {
|
||||
applyBadges(plugin);
|
||||
applyBadges();
|
||||
}, 3000);
|
||||
|
||||
plugin.register(() => {
|
||||
window.clearInterval(interval);
|
||||
});
|
||||
|
||||
// Return cleanup
|
||||
return () => {
|
||||
window.clearInterval(interval);
|
||||
const el = document.getElementById(STYLE_ID);
|
||||
if (el) el.remove();
|
||||
removeBadges();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a file path as encrypted (call after encrypting).
|
||||
*/
|
||||
export function markFileEncrypted(filePath: string): void {
|
||||
encryptedPaths.add(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmark a file path as encrypted (call after decrypting).
|
||||
*/
|
||||
export function markFileDecrypted(filePath: string): void {
|
||||
encryptedPaths.delete(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan vault for encrypted binary files (check first 4 bytes for OCKE magic).
|
||||
*/
|
||||
async function scanVaultForEncryptedFiles(
|
||||
plugin: Plugin,
|
||||
originalReadBinary: (path: string) => Promise<ArrayBuffer>
|
||||
): Promise<void> {
|
||||
const files = plugin.app.vault.getFiles();
|
||||
|
||||
for (const file of files) {
|
||||
if (file.path.endsWith('.md')) continue;
|
||||
|
||||
try {
|
||||
const data = await originalReadBinary(file.path);
|
||||
const bytes = new Uint8Array(data, 0, Math.min(4, data.byteLength));
|
||||
|
|
@ -97,19 +60,13 @@ async function scanVaultForEncryptedFiles(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply CSS class to file explorer items for encrypted files.
|
||||
*/
|
||||
function applyBadges(_plugin: Plugin): void {
|
||||
const fileExplorer = document.querySelectorAll('.nav-file');
|
||||
|
||||
function applyBadges(): void {
|
||||
const fileExplorer = activeDocument.querySelectorAll('.nav-file');
|
||||
fileExplorer.forEach((el) => {
|
||||
const titleEl = el.querySelector('.nav-file-title');
|
||||
if (!titleEl) return;
|
||||
|
||||
const path = titleEl.getAttribute('data-path');
|
||||
if (!path) return;
|
||||
|
||||
if (encryptedPaths.has(path)) {
|
||||
el.classList.add(ENCRYPTED_CLASS);
|
||||
} else {
|
||||
|
|
@ -118,11 +75,8 @@ function applyBadges(_plugin: Plugin): void {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all badges.
|
||||
*/
|
||||
function removeBadges(): void {
|
||||
document.querySelectorAll(`.${ENCRYPTED_CLASS}`).forEach((el) => {
|
||||
activeDocument.querySelectorAll(`.${ENCRYPTED_CLASS}`).forEach((el) => {
|
||||
el.classList.remove(ENCRYPTED_CLASS);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
/**
|
||||
* Markdown code block processor for ```ocke-v1 blocks.
|
||||
*
|
||||
* Registers a code block processor that renders encrypted blocks
|
||||
* as visual widgets in both Reading view and Live Preview mode.
|
||||
*
|
||||
* Key principle: the actual document text is NEVER modified.
|
||||
* The processor only controls how the block is rendered visually.
|
||||
*
|
||||
* - If decryption succeeds: shows 🔓 header + decrypted plaintext
|
||||
* - If decryption fails: shows 🔒 header + truncated ciphertext
|
||||
* Renders encrypted blocks as visual widgets in Reading view and Live Preview.
|
||||
*/
|
||||
|
||||
import { Plugin, MarkdownPostProcessorContext } from 'obsidian';
|
||||
|
|
@ -17,17 +9,8 @@ import { decodeInlineBlock } from '../format/inline-codec';
|
|||
import { parse } from '../format/parser';
|
||||
import { FORMAT_VERSION } from '../constants';
|
||||
|
||||
/**
|
||||
* Cache of decrypted content keyed by base64 content.
|
||||
*/
|
||||
const decryptionCache = new Map<string, { plaintext: string | null; error: boolean }>();
|
||||
|
||||
/**
|
||||
* Register the ocke-v1 code block processor.
|
||||
*
|
||||
* This makes Obsidian render ```ocke-v1 blocks with a custom visual
|
||||
* instead of showing raw base64 text.
|
||||
*/
|
||||
export function registerSecretBlockProcessor(
|
||||
plugin: Plugin,
|
||||
cryptoEngine: CryptoEngine,
|
||||
|
|
@ -39,32 +22,10 @@ export function registerSecretBlockProcessor(
|
|||
async (source: string, el: HTMLElement, _ctx: MarkdownPostProcessorContext) => {
|
||||
const base64Content = source.trim();
|
||||
|
||||
// Create container
|
||||
const container = el.createDiv({ cls: 'ocke-secret-block' });
|
||||
container.style.border = '1px solid var(--background-modifier-border)';
|
||||
container.style.borderRadius = '4px';
|
||||
container.style.overflow = 'hidden';
|
||||
|
||||
// Header
|
||||
const header = container.createDiv({ cls: 'ocke-secret-block-header' });
|
||||
header.style.padding = '4px 8px';
|
||||
header.style.fontSize = '0.8em';
|
||||
header.style.fontWeight = 'bold';
|
||||
header.style.display = 'flex';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.gap = '4px';
|
||||
|
||||
// Content area
|
||||
const content = container.createEl('pre', { cls: 'ocke-secret-block-content' });
|
||||
content.style.padding = '8px';
|
||||
content.style.margin = '0';
|
||||
content.style.whiteSpace = 'pre-wrap';
|
||||
content.style.wordBreak = 'break-word';
|
||||
content.style.fontFamily = 'var(--font-monospace)';
|
||||
content.style.fontSize = '0.9em';
|
||||
content.style.background = 'var(--background-secondary)';
|
||||
|
||||
// Check cache first
|
||||
const cached = decryptionCache.get(base64Content);
|
||||
if (cached) {
|
||||
if (cached.error) {
|
||||
|
|
@ -75,14 +36,11 @@ export function registerSecretBlockProcessor(
|
|||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
header.style.background = 'var(--background-modifier-border)';
|
||||
header.style.color = 'var(--text-muted)';
|
||||
header.addClass('ocke-secret-block-header--loading');
|
||||
header.textContent = '⏳ secret (decrypting...)';
|
||||
content.textContent = '...';
|
||||
content.style.color = 'var(--text-muted)';
|
||||
content.addClass('ocke-secret-block-content--locked');
|
||||
|
||||
// Attempt decryption
|
||||
try {
|
||||
const fullBlock = '```ocke-v1\n' + base64Content + '\n```';
|
||||
const binaryData = decodeInlineBlock(fullBlock);
|
||||
|
|
@ -114,30 +72,25 @@ export function registerSecretBlockProcessor(
|
|||
}
|
||||
|
||||
function renderDecrypted(header: HTMLElement, content: HTMLElement, plaintext: string): void {
|
||||
header.style.background = 'var(--interactive-accent)';
|
||||
header.style.color = 'var(--text-on-accent)';
|
||||
header.removeClass('ocke-secret-block-header--loading');
|
||||
header.removeClass('ocke-secret-block-header--locked');
|
||||
header.addClass('ocke-secret-block-header--decrypted');
|
||||
header.textContent = '🔓 secret (decrypted)';
|
||||
|
||||
content.removeClass('ocke-secret-block-content--locked');
|
||||
content.textContent = plaintext;
|
||||
content.style.color = '';
|
||||
}
|
||||
|
||||
function renderLocked(header: HTMLElement, content: HTMLElement, base64Content: string): void {
|
||||
header.style.background = 'var(--background-modifier-error)';
|
||||
header.style.color = 'var(--text-error)';
|
||||
header.removeClass('ocke-secret-block-header--loading');
|
||||
header.removeClass('ocke-secret-block-header--decrypted');
|
||||
header.addClass('ocke-secret-block-header--locked');
|
||||
header.textContent = '🔒 secret (locked — no key access)';
|
||||
|
||||
const preview = base64Content.length > 80
|
||||
? base64Content.slice(0, 80) + '…'
|
||||
: base64Content;
|
||||
const preview = base64Content.length > 80 ? base64Content.slice(0, 80) + '…' : base64Content;
|
||||
content.addClass('ocke-secret-block-content--locked');
|
||||
content.textContent = preview;
|
||||
content.style.color = 'var(--text-muted)';
|
||||
content.style.fontStyle = 'italic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the decryption cache.
|
||||
*/
|
||||
export function clearDecryptionCache(): void {
|
||||
decryptionCache.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,22 +55,20 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl("h2", { text: "Cloud KMS Encryption Settings" });
|
||||
new Setting(containerEl).setName("Cloud KMS Encryption Settings").setHeading();
|
||||
|
||||
// --- Keys section ---
|
||||
containerEl.createEl("h3", { text: "Encryption Keys" });
|
||||
new Setting(containerEl).setName("Encryption Keys").setHeading();
|
||||
containerEl.createEl("p", {
|
||||
text: "Add KMS keys with aliases. Use aliases in %%secret-start:alias%% markers. The default key is used when no alias is specified.",
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
|
||||
this.addKeysSection(containerEl);
|
||||
|
||||
// --- Default key ---
|
||||
this.addDefaultKeySetting(containerEl);
|
||||
|
||||
// --- Legacy single key (backward compat) ---
|
||||
containerEl.createEl("h3", { text: "Legacy Settings" });
|
||||
// --- Legacy ---
|
||||
new Setting(containerEl).setName("Legacy Settings").setHeading();
|
||||
containerEl.createEl("p", {
|
||||
text: "Used as fallback if no keys are configured above.",
|
||||
cls: "setting-item-description",
|
||||
|
|
@ -78,7 +76,7 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
this.addArnSetting(containerEl);
|
||||
|
||||
// --- Behavior ---
|
||||
containerEl.createEl("h3", { text: "Behavior" });
|
||||
new Setting(containerEl).setName("Behavior").setHeading();
|
||||
this.addAutoDecryptBlocksSetting(containerEl);
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +110,7 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
this.plugin.settings.keys[index].alias = value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
||||
await this.plugin.saveData(this.plugin.settings);
|
||||
});
|
||||
text.inputEl.style.width = "120px";
|
||||
text.inputEl.addClass('ocke-input-alias');
|
||||
})
|
||||
.addText((text) => {
|
||||
text
|
||||
|
|
@ -122,7 +120,7 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
this.plugin.settings.keys[index].arn = value.slice(0, ARN_MAX_LENGTH);
|
||||
await this.plugin.saveData(this.plugin.settings);
|
||||
});
|
||||
text.inputEl.style.width = "300px";
|
||||
text.inputEl.addClass('ocke-input-arn');
|
||||
})
|
||||
.addButton((btn) => {
|
||||
btn.setButtonText("✕").setWarning().onClick(async () => {
|
||||
|
|
@ -138,12 +136,10 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
|
||||
// Validation
|
||||
if (key.arn && !validateAwsKmsArn(key.arn).valid) {
|
||||
const errorEl = setting.controlEl.createEl("div", {
|
||||
setting.controlEl.createEl("div", {
|
||||
text: "Invalid ARN format",
|
||||
cls: "setting-error-message",
|
||||
cls: "ocke-setting-error",
|
||||
});
|
||||
errorEl.style.color = "var(--text-error)";
|
||||
errorEl.style.fontSize = "0.8em";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,18 +181,15 @@ export class CloudKmsSettingsTab extends PluginSettingTab {
|
|||
if (stripped.length > 0 && !validateAwsKmsArn(stripped).valid) {
|
||||
errorEl = setting.controlEl.createEl("div", {
|
||||
text: "Invalid AWS KMS key ARN format",
|
||||
cls: "setting-error-message",
|
||||
cls: "ocke-setting-error",
|
||||
});
|
||||
errorEl.style.color = "var(--text-error)";
|
||||
errorEl.style.fontSize = "0.85em";
|
||||
errorEl.style.marginTop = "4px";
|
||||
}
|
||||
|
||||
this.plugin.settings.awsCmkArn = clamped;
|
||||
await this.plugin.saveData(this.plugin.settings);
|
||||
});
|
||||
text.inputEl.maxLength = ARN_MAX_LENGTH;
|
||||
text.inputEl.style.width = "100%";
|
||||
text.inputEl.addClass('ocke-input-wide');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,21 +46,22 @@ export function installStatusBar(
|
|||
};
|
||||
|
||||
const setStatus = (status: 'ok' | 'error' | 'checking', detail: string) => {
|
||||
statusBarEl.removeClass('ocke-status-bar--ok', 'ocke-status-bar--error', 'ocke-status-bar--checking');
|
||||
switch (status) {
|
||||
case 'ok':
|
||||
statusBarEl.setText('🔓 KMS');
|
||||
statusBarEl.setAttribute('title', 'KMS connection OK — encryption/decryption available');
|
||||
statusBarEl.style.color = '';
|
||||
statusBarEl.addClass('ocke-status-bar--ok');
|
||||
break;
|
||||
case 'error':
|
||||
statusBarEl.setText('🔒 KMS ⚠️');
|
||||
statusBarEl.setAttribute('title', `KMS unavailable: ${detail}\nSecret blocks will NOT be encrypted on save!`);
|
||||
statusBarEl.style.color = 'var(--text-error)';
|
||||
statusBarEl.addClass('ocke-status-bar--error');
|
||||
break;
|
||||
case 'checking':
|
||||
statusBarEl.setText('⏳ KMS');
|
||||
statusBarEl.setAttribute('title', 'Checking KMS connection...');
|
||||
statusBarEl.style.color = 'var(--text-muted)';
|
||||
statusBarEl.addClass('ocke-status-bar--checking');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -72,7 +73,7 @@ export function installStatusBar(
|
|||
|
||||
// Initial check after layout ready
|
||||
plugin.app.workspace.onLayoutReady(() => {
|
||||
setTimeout(checkStatus, 2000);
|
||||
window.setTimeout(checkStatus, 2000);
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
|
|
|||
121
styles.css
Normal file
121
styles.css
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* Cloud KMS Encryption Plugin Styles */
|
||||
|
||||
/* Secret block processor & widget */
|
||||
.ocke-secret-block {
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ocke-secret-block-header {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ocke-secret-block-header--decrypted {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.ocke-secret-block-header--locked {
|
||||
background: var(--background-modifier-error);
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
.ocke-secret-block-header--loading {
|
||||
background: var(--background-modifier-border);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.ocke-secret-block-content {
|
||||
padding: 8px;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
.ocke-secret-block-content--locked {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Encrypted file view */
|
||||
.ocke-encrypted-view-banner {
|
||||
background-color: var(--background-modifier-error);
|
||||
color: var(--text-on-accent);
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ocke-encrypted-view-banner-detail {
|
||||
margin-top: 4px;
|
||||
font-weight: 400;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ocke-encrypted-view-heading {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ocke-encrypted-view-code {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
padding: 12px;
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
font-family: var(--font-monospace);
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* File explorer badge */
|
||||
.nav-file.ocke-encrypted-file .nav-file-title-content::after {
|
||||
content: ' 🔒';
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Settings errors */
|
||||
.ocke-setting-error {
|
||||
color: var(--text-error);
|
||||
font-size: 0.85em;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Status bar */
|
||||
.ocke-status-bar--ok {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.ocke-status-bar--error {
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
.ocke-status-bar--checking {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Settings inputs */
|
||||
.ocke-input-alias {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.ocke-input-arn {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.ocke-input-wide {
|
||||
width: 100%;
|
||||
}
|
||||
Loading…
Reference in a new issue