secubox-openwrt/package/secubox/luci-app-hexojs/htdocs/luci-static/resources/view/hexojs/sync.js
CyberMind-FR 170f4a90e2 feat(luci-hexojs): Add Gitea integration to sync page
- Add gitea_status, gitea_sync, gitea_clone, gitea_save_config RPCD methods
- Add Gitea section to sync.js with config form and sync buttons
- Update ACL for new Gitea methods
- Fix luci-app-metabolizer install section for RPCD executable

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 13:00:05 +01:00

688 lines
24 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require rpc';
'require hexojs/api as api';
var callGiteaStatus = rpc.declare({
object: 'luci.hexojs',
method: 'gitea_status',
expect: {}
});
var callGiteaSync = rpc.declare({
object: 'luci.hexojs',
method: 'gitea_sync',
expect: {}
});
var callGiteaClone = rpc.declare({
object: 'luci.hexojs',
method: 'gitea_clone',
expect: {}
});
var callGiteaSaveConfig = rpc.declare({
object: 'luci.hexojs',
method: 'gitea_save_config',
params: ['enabled', 'gitea_url', 'gitea_user', 'gitea_token', 'content_repo', 'content_branch', 'auto_sync']
});
return view.extend({
title: _('Content Sync'),
wizardStep: 0,
load: function() {
return Promise.all([
api.getGitSyncData(),
callGiteaStatus()
]);
},
// ============================================
// Wizard Actions
// ============================================
handleClone: function() {
var self = this;
var repo = document.querySelector('#git-repo').value;
var branch = document.querySelector('#git-branch').value || 'main';
if (!repo) {
ui.addNotification(null, E('p', _('Repository URL is required')), 'error');
return;
}
ui.showModal(_('Cloning Repository'), [
E('p', { 'class': 'spinning' }, _('Cloning from GitHub. This may take a moment...'))
]);
api.gitClone(repo, branch).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Repository cloned successfully!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Clone failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handleInit: function() {
var self = this;
var repo = document.querySelector('#git-repo-init').value;
var branch = document.querySelector('#git-branch-init').value || 'main';
if (!repo) {
ui.addNotification(null, E('p', _('Repository URL is required')), 'error');
return;
}
ui.showModal(_('Initializing Repository'), [
E('p', { 'class': 'spinning' }, _('Initializing git and setting remote...'))
]);
api.gitInit(repo, branch).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Repository initialized!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Init failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handlePull: function() {
ui.showModal(_('Pulling Changes'), [
E('p', { 'class': 'spinning' }, _('Pulling latest changes from remote...'))
]);
api.gitPull().then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Pull successful!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Pull failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handlePush: function() {
var self = this;
var message = document.querySelector('#git-commit-msg').value;
ui.showModal(_('Pushing Changes'), [
E('p', { 'class': 'spinning' }, _('Committing and pushing changes to remote...'))
]);
api.gitPush(message, false).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Push successful!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Push failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handleFetch: function() {
ui.showModal(_('Fetching'), [
E('p', { 'class': 'spinning' }, _('Fetching updates from remote...'))
]);
api.gitFetch().then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Fetch complete!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Fetch failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handleReset: function(hard) {
var self = this;
if (hard) {
ui.showModal(_('Confirm Reset'), [
E('p', {}, _('This will discard ALL local changes. Are you sure?')),
E('div', { 'class': 'right', 'style': 'margin-top: 16px;' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'btn cbi-button-negative',
'style': 'margin-left: 8px;',
'click': function() {
ui.showModal(_('Resetting'), [
E('p', { 'class': 'spinning' }, _('Discarding all local changes...'))
]);
api.gitReset(true).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Reset complete!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Reset failed')), 'error');
}
});
}
}, _('Reset'))
])
]);
} else {
api.gitReset(false).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Changes unstaged')), 'info');
location.reload();
}
});
}
},
handleSaveCredentials: function() {
var name = document.querySelector('#git-name').value;
var email = document.querySelector('#git-email').value;
api.gitSetCredentials(name, email).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Git credentials saved!')), 'info');
} else {
ui.addNotification(null, E('p', result.error || _('Failed')), 'error');
}
});
},
// ============================================
// Gitea Actions
// ============================================
handleGiteaSync: function() {
ui.showModal(_('Syncing from Gitea'), [
E('p', { 'class': 'spinning' }, _('Pulling content from Gitea...'))
]);
callGiteaSync().then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Content synced from Gitea!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Sync failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handleGiteaClone: function() {
ui.showModal(_('Cloning from Gitea'), [
E('p', { 'class': 'spinning' }, _('Cloning content repository from Gitea...'))
]);
callGiteaClone().then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Content cloned from Gitea!')), 'info');
location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Clone failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
handleGiteaSaveConfig: function() {
var enabled = document.querySelector('#gitea-enabled').checked ? '1' : '0';
var url = document.querySelector('#gitea-url').value;
var user = document.querySelector('#gitea-user').value;
var token = document.querySelector('#gitea-token').value;
var repo = document.querySelector('#gitea-repo').value;
var branch = document.querySelector('#gitea-branch').value || 'main';
callGiteaSaveConfig(enabled, url, user, token, repo, branch, '0').then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Gitea configuration saved!')), 'info');
} else {
ui.addNotification(null, E('p', result.error || _('Save failed')), 'error');
}
});
},
// ============================================
// Render
// ============================================
render: function(data) {
var self = this;
var gitData = data[0] || {};
var giteaStatus = data[1] || {};
var status = gitData.status || {};
var credentials = gitData.credentials || {};
var commits = gitData.commits || [];
var isRepo = status.is_repo;
return E('div', { 'class': 'hexo-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('hexojs/dashboard.css') }),
// Header
E('div', { 'class': 'hexo-header' }, [
E('div', { 'class': 'hexo-card-title' }, [
E('span', { 'class': 'hexo-card-title-icon' }, '\uD83D\uDD04'),
_('Content Sync')
]),
isRepo ? E('div', { 'class': 'hexo-status-badge ' + (status.ahead > 0 ? 'warning' : 'running') }, [
E('span', { 'class': 'hexo-status-dot' }),
status.branch || 'main'
]) : ''
]),
// Gitea Integration Card
E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, [
E('span', { 'style': 'margin-right: 8px;' }, '\uD83E\uDD8A'),
_('Gitea Content Sync')
]),
giteaStatus.enabled ? E('div', { 'class': 'hexo-status-badge running' }, [
E('span', { 'class': 'hexo-status-dot' }),
_('Enabled')
]) : E('div', { 'class': 'hexo-status-badge stopped' }, [
E('span', { 'class': 'hexo-status-dot' }),
_('Disabled')
])
]),
E('p', { 'style': 'margin-bottom: 16px; color: var(--hexo-text-muted);' },
_('Sync blog content from your local Gitea server.')),
// Gitea Status
giteaStatus.has_local_repo ? E('div', { 'style': 'background: var(--hexo-bg-input); padding: 12px; border-radius: 8px; margin-bottom: 16px;' }, [
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;' }, [
E('div', {}, [
E('div', { 'style': 'font-size: 11px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Repository')),
E('div', { 'style': 'font-size: 14px;' }, giteaStatus.content_repo || '-')
]),
E('div', {}, [
E('div', { 'style': 'font-size: 11px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Branch')),
E('div', { 'style': 'font-size: 14px;' }, giteaStatus.local_branch || '-')
]),
E('div', {}, [
E('div', { 'style': 'font-size: 11px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Last Commit')),
E('div', { 'style': 'font-size: 13px;' }, giteaStatus.last_commit || '-')
])
])
]) : '',
// Gitea Actions
E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px;' }, [
giteaStatus.has_local_repo ?
E('button', {
'class': 'hexo-btn hexo-btn-primary',
'click': function() { self.handleGiteaSync(); },
'disabled': !giteaStatus.enabled
}, ['\uD83D\uDD04 ', _('Sync from Gitea')]) :
E('button', {
'class': 'hexo-btn hexo-btn-success',
'click': function() { self.handleGiteaClone(); },
'disabled': !giteaStatus.enabled
}, ['\uD83D\uDCE5 ', _('Clone from Gitea')])
]),
// Gitea Config Form
E('details', { 'style': 'margin-top: 8px;' }, [
E('summary', { 'style': 'cursor: pointer; color: var(--hexo-primary);' }, _('Configure Gitea Connection')),
E('div', { 'style': 'margin-top: 12px; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;' }, [
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('input', {
'type': 'checkbox',
'id': 'gitea-enabled',
'checked': giteaStatus.enabled
}),
_('Enable Gitea Sync')
])
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Gitea URL')),
E('input', {
'type': 'text',
'id': 'gitea-url',
'class': 'hexo-input',
'value': giteaStatus.gitea_url || '',
'placeholder': 'http://192.168.255.1:3000'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Username')),
E('input', {
'type': 'text',
'id': 'gitea-user',
'class': 'hexo-input',
'value': giteaStatus.gitea_user || '',
'placeholder': 'admin'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Access Token')),
E('input', {
'type': 'password',
'id': 'gitea-token',
'class': 'hexo-input',
'placeholder': _('Gitea access token')
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Content Repository')),
E('input', {
'type': 'text',
'id': 'gitea-repo',
'class': 'hexo-input',
'value': giteaStatus.content_repo || '',
'placeholder': 'blog-content'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Branch')),
E('input', {
'type': 'text',
'id': 'gitea-branch',
'class': 'hexo-input',
'value': giteaStatus.content_branch || 'main',
'placeholder': 'main'
})
])
]),
E('button', {
'class': 'hexo-btn hexo-btn-secondary',
'style': 'margin-top: 12px;',
'click': function() { self.handleGiteaSaveConfig(); }
}, _('Save Gitea Config'))
])
]),
// Divider
E('div', { 'style': 'border-top: 1px solid var(--hexo-border); margin: 24px 0;' }),
// GitHub Sync Header
E('div', { 'class': 'hexo-card-title', 'style': 'margin-bottom: 16px;' }, [
E('span', { 'class': 'hexo-card-title-icon' }, '\uD83D\uDC19'),
_('GitHub / Git Sync')
]),
// Setup Wizard (shown if not a repo)
!isRepo ? E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, _('Setup GitHub Sync'))
]),
E('p', { 'style': 'margin-bottom: 20px; color: var(--hexo-text-muted);' },
_('Connect your Hexo site to a GitHub repository for version control and backup.')),
// Option 1: Clone existing repo
E('div', { 'class': 'hexo-card', 'style': 'background: var(--hexo-bg-input); margin-bottom: 16px;' }, [
E('h4', { 'style': 'margin-bottom: 12px;' }, [
'\uD83D\uDCE5 ', _('Option 1: Clone Existing Repository')
]),
E('p', { 'style': 'font-size: 13px; color: var(--hexo-text-muted); margin-bottom: 12px;' },
_('Download an existing Hexo site from GitHub. This will replace your current site.')),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr auto auto; gap: 8px; align-items: end;' }, [
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Repository URL')),
E('input', {
'type': 'text',
'id': 'git-repo',
'class': 'hexo-input',
'placeholder': 'https://github.com/username/repo.git'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0; width: 120px;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Branch')),
E('input', {
'type': 'text',
'id': 'git-branch',
'class': 'hexo-input',
'value': 'main',
'placeholder': 'main'
})
]),
E('button', {
'class': 'hexo-btn hexo-btn-primary',
'style': 'height: 40px;',
'click': function() { self.handleClone(); }
}, _('Clone'))
])
]),
// Option 2: Initialize for existing site
E('div', { 'class': 'hexo-card', 'style': 'background: var(--hexo-bg-input);' }, [
E('h4', { 'style': 'margin-bottom: 12px;' }, [
'\uD83D\uDCE4 ', _('Option 2: Push Existing Site to GitHub')
]),
E('p', { 'style': 'font-size: 13px; color: var(--hexo-text-muted); margin-bottom: 12px;' },
_('Create a new repository on GitHub first, then connect your existing site to it.')),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr auto auto; gap: 8px; align-items: end;' }, [
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Repository URL')),
E('input', {
'type': 'text',
'id': 'git-repo-init',
'class': 'hexo-input',
'placeholder': 'git@github.com:username/repo.git'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0; width: 120px;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Branch')),
E('input', {
'type': 'text',
'id': 'git-branch-init',
'class': 'hexo-input',
'value': 'main',
'placeholder': 'main'
})
]),
E('button', {
'class': 'hexo-btn hexo-btn-success',
'style': 'height: 40px;',
'click': function() { self.handleInit(); }
}, _('Initialize'))
])
])
]) : '',
// Sync Dashboard (shown when repo exists)
isRepo ? E('div', {}, [
// Status Card
E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, _('Repository Status')),
E('button', {
'class': 'hexo-btn hexo-btn-sm hexo-btn-secondary',
'click': function() { self.handleFetch(); }
}, ['\uD83D\uDD04 ', _('Refresh')])
]),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px;' }, [
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Remote')),
E('div', { 'style': 'font-size: 14px; word-break: break-all;' }, status.remote || _('Not configured'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Branch')),
E('div', { 'style': 'font-size: 14px;' }, status.branch || '-')
]),
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Modified')),
E('div', { 'style': 'font-size: 14px;' }, (status.modified || 0) + ' ' + _('files'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Untracked')),
E('div', { 'style': 'font-size: 14px;' }, (status.untracked || 0) + ' ' + _('files'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Ahead/Behind')),
E('div', { 'style': 'font-size: 14px;' }, [
E('span', { 'style': status.ahead > 0 ? 'color: var(--hexo-warning);' : '' }, '\u2191' + (status.ahead || 0)),
' / ',
E('span', { 'style': status.behind > 0 ? 'color: var(--hexo-primary);' : '' }, '\u2193' + (status.behind || 0))
])
]),
E('div', {}, [
E('div', { 'style': 'font-size: 12px; color: var(--hexo-text-muted); text-transform: uppercase;' }, _('Last Commit')),
E('div', { 'style': 'font-size: 13px;' }, status.last_commit || '-')
])
])
]),
// Sync Actions
E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, _('Sync Actions'))
]),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px;' }, [
// Pull Section
E('div', { 'class': 'hexo-card', 'style': 'background: var(--hexo-bg-input);' }, [
E('h4', { 'style': 'margin-bottom: 8px;' }, ['\u2B07 ', _('Pull Changes')]),
E('p', { 'style': 'font-size: 13px; color: var(--hexo-text-muted); margin-bottom: 12px;' },
_('Download latest changes from GitHub.')),
E('button', {
'class': 'hexo-btn hexo-btn-primary',
'click': function() { self.handlePull(); },
'disabled': !status.remote
}, _('Pull'))
]),
// Push Section
E('div', { 'class': 'hexo-card', 'style': 'background: var(--hexo-bg-input);' }, [
E('h4', { 'style': 'margin-bottom: 8px;' }, ['\u2B06 ', _('Push Changes')]),
E('p', { 'style': 'font-size: 13px; color: var(--hexo-text-muted); margin-bottom: 12px;' },
_('Upload your changes to GitHub.')),
E('div', { 'class': 'hexo-form-group', 'style': 'margin-bottom: 8px;' }, [
E('input', {
'type': 'text',
'id': 'git-commit-msg',
'class': 'hexo-input',
'placeholder': _('Commit message (optional)')
})
]),
E('button', {
'class': 'hexo-btn hexo-btn-success',
'click': function() { self.handlePush(); },
'disabled': !status.remote
}, _('Commit & Push'))
])
])
]),
// Git Credentials
E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, _('Git Identity'))
]),
E('p', { 'style': 'margin-bottom: 12px; color: var(--hexo-text-muted);' },
_('Configure your git identity for commits.')),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr auto; gap: 12px; align-items: end;' }, [
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Name')),
E('input', {
'type': 'text',
'id': 'git-name',
'class': 'hexo-input',
'value': credentials.name || '',
'placeholder': 'Your Name'
})
]),
E('div', { 'class': 'hexo-form-group', 'style': 'margin: 0;' }, [
E('label', { 'class': 'hexo-form-label' }, _('Email')),
E('input', {
'type': 'email',
'id': 'git-email',
'class': 'hexo-input',
'value': credentials.email || '',
'placeholder': 'you@example.com'
})
]),
E('button', {
'class': 'hexo-btn hexo-btn-secondary',
'style': 'height: 40px;',
'click': function() { self.handleSaveCredentials(); }
}, _('Save'))
])
]),
// Recent Commits
commits.length > 0 ? E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, _('Recent Commits'))
]),
E('div', { 'class': 'hexo-commits' },
commits.slice(0, 10).map(function(commit) {
return E('div', { 'class': 'hexo-commit', 'style': 'padding: 8px 0; border-bottom: 1px solid var(--hexo-border);' }, [
E('code', { 'style': 'color: var(--hexo-primary); margin-right: 8px;' }, commit.hash),
E('span', {}, commit.message)
]);
})
)
]) : '',
// Danger Zone
E('div', { 'class': 'hexo-card', 'style': 'border-color: var(--hexo-danger);' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title', 'style': 'color: var(--hexo-danger);' }, [
'\u26A0 ', _('Danger Zone')
])
]),
E('p', { 'style': 'margin-bottom: 12px; color: var(--hexo-text-muted);' },
_('These actions can cause data loss. Use with caution.')),
E('div', { 'class': 'hexo-actions' }, [
E('button', {
'class': 'hexo-btn hexo-btn-secondary',
'click': function() { self.handleReset(false); }
}, _('Unstage All')),
E('button', {
'class': 'hexo-btn hexo-btn-danger',
'click': function() { self.handleReset(true); }
}, _('Discard All Changes'))
])
])
]) : '',
// SSH Key Info
E('div', { 'class': 'hexo-card' }, [
E('div', { 'class': 'hexo-card-header' }, [
E('div', { 'class': 'hexo-card-title' }, ['\uD83D\uDD11 ', _('SSH Key Setup')])
]),
E('p', { 'style': 'color: var(--hexo-text-muted); font-size: 13px;' },
_('For private repositories, add your SSH public key to GitHub. Generate a key on your router with:')),
E('pre', { 'style': 'background: var(--hexo-bg-input); padding: 12px; border-radius: 6px; margin-top: 8px; font-size: 13px;' },
'ssh-keygen -t ed25519 -C "your@email.com"\ncat ~/.ssh/id_ed25519.pub'),
E('p', { 'style': 'color: var(--hexo-text-muted); font-size: 13px; margin-top: 8px;' }, [
_('Then add this key to '),
E('a', { 'href': 'https://github.com/settings/keys', 'target': '_blank' }, 'GitHub Settings > SSH Keys')
])
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});