-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wrote a simple interface to control Clash from the LUCI interface
luci-app-ssclash
- Loading branch information
1 parent
1c20c1e
commit 35847de
Showing
7 changed files
with
303 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"admin/services/ssclash": { | ||
"title": "ssclash", | ||
"action": { | ||
"type": "alias", | ||
"path": "admin/services/ssclash/config" | ||
}, | ||
"depends": { | ||
"acl": [ "luci-app-ssclash" ] | ||
} | ||
}, | ||
"admin/services/ssclash/config": { | ||
"title": "Config", | ||
"order": 10, | ||
"action": { | ||
"type": "view", | ||
"path": "ssclash/config" | ||
} | ||
}, | ||
"admin/services/ssclash/log": { | ||
"title": "Log", | ||
"order": 20, | ||
"action": { | ||
"type": "view", | ||
"path": "ssclash/log" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"luci-app-ssclash": { | ||
"description": "Grant access to Clash procedures", | ||
"read": { | ||
"file": { | ||
"/opt/clash/config.yaml": [ "read" ], | ||
"/sbin/logread": [ "exec" ] | ||
}, | ||
"ubus": { | ||
"file": [ "read" ], | ||
"service": [ "list" ] | ||
} | ||
}, | ||
"write": { | ||
"file": { | ||
"/opt/clash/config.yaml": [ "write" ], | ||
"/etc/init.d/clash start": [ "exec" ], | ||
"/etc/init.d/clash stop": [ "exec" ], | ||
"/etc/init.d/clash reload": [ "exec" ] | ||
}, | ||
"ubus": { | ||
"file": [ "write" ] | ||
} | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
rootfs/www/luci-static/resources/view/ssclash/config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
'use strict'; | ||
'require view'; | ||
'require fs'; | ||
'require ui'; | ||
'require rpc'; | ||
|
||
var isReadonlyView = !L.hasViewPermission() || null; | ||
let startStopButton = null; | ||
|
||
const callServiceList = rpc.declare({ | ||
object: 'service', | ||
method: 'list', | ||
params: ['name'], | ||
expect: { '': {} } | ||
}); | ||
|
||
async function getServiceStatus() { | ||
try { | ||
return Object.values((await callServiceList('clash'))['clash']['instances'])[0]?.running; | ||
} catch (ignored) { | ||
return false; | ||
} | ||
} | ||
|
||
async function startService() { | ||
if (startStopButton) startStopButton.disabled = true; | ||
return fs.exec('/etc/init.d/clash', ['start']) | ||
.catch(function(e) { | ||
ui.addNotification(null, E('p', _('Unable to start service: %s').format(e.message)), 'error'); | ||
}) | ||
.finally(() => { | ||
if (startStopButton) startStopButton.disabled = false; | ||
}); | ||
} | ||
|
||
async function stopService() { | ||
if (startStopButton) startStopButton.disabled = true; | ||
return fs.exec('/etc/init.d/clash', ['stop']) | ||
.catch(function(e) { | ||
ui.addNotification(null, E('p', _('Unable to stop service: %s').format(e.message)), 'error'); | ||
}) | ||
.finally(() => { | ||
if (startStopButton) startStopButton.disabled = false; | ||
}); | ||
} | ||
|
||
async function toggleService() { | ||
const running = await getServiceStatus(); | ||
if (running) { | ||
await stopService(); | ||
} else { | ||
await startService(); | ||
} | ||
window.location.reload(); | ||
} | ||
|
||
async function openDashboard() { | ||
let newWindow = window.open('', '_blank'); | ||
const running = await getServiceStatus(); | ||
if (running) { | ||
let url = `http://${window.location.hostname}:9090/ui/?hostname=${window.location.hostname}&port=9090`; | ||
newWindow.location.href = url; | ||
} else { | ||
newWindow.close(); | ||
alert(_('Service is not running.')); | ||
} | ||
} | ||
|
||
return view.extend({ | ||
load: function() { | ||
return L.resolveDefault(fs.read('/opt/clash/config.yaml'), ''); | ||
}, | ||
handleSaveApply: function(ev) { | ||
var value = (document.querySelector('textarea').value || '').trim().replace(/\r\n/g, '\n') + '\n'; | ||
return fs.write('/opt/clash/config.yaml', value).then(function(rc) { | ||
document.querySelector('textarea').value = value; | ||
ui.addNotification(null, E('p', _('Contents have been saved.')), 'info'); | ||
return fs.exec('/etc/init.d/clash', ['reload']); | ||
}).then(function() { | ||
window.location.reload(); | ||
}).catch(function(e) { | ||
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)), 'error'); | ||
}); | ||
}, | ||
render: async function(config) { | ||
const running = await getServiceStatus(); | ||
|
||
return E([ | ||
E('button', { | ||
'class': 'btn', | ||
'click': openDashboard | ||
}, _('Open Dashboard')), | ||
(startStopButton = E('button', { | ||
'class': 'btn', | ||
'click': toggleService, | ||
'style': 'margin-left: 10px;' | ||
}, running ? _('Stop service') : _('Start service'))), | ||
E('span', { | ||
'style': running ? 'color: green; margin-left: 10px;' : 'color: red; margin-left: 10px;' | ||
}, running ? _('Clash is running') : _('Clash stopped')), | ||
E('h2', _('Clash config')), | ||
E('p', { 'class': 'cbi-section-descr' }, _('Your current Clash config. When applied, the changes will be saved and the service will be restarted.')), | ||
E('textarea', { | ||
'style': 'width: 100% !important; padding: 5px; font-family: monospace', | ||
'rows': 35, | ||
'disabled': isReadonlyView | ||
}, [config != null ? config : '']) | ||
]); | ||
}, | ||
handleSave: null, | ||
handleReset: null | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
'use strict'; | ||
'require view'; | ||
'require poll'; | ||
'require fs'; | ||
|
||
return view.extend({ | ||
load: function () { | ||
return fs.stat('/sbin/logread'); | ||
}, | ||
|
||
render: function (stat) { | ||
const loggerPath = stat && stat.path ? stat.path : null; | ||
|
||
poll.add(() => { | ||
if (loggerPath) { | ||
return fs.exec_direct(loggerPath, ['-e', 'clash']) | ||
.then(res => { | ||
const log = document.getElementById('logfile'); | ||
// Without log processing | ||
// log.value = res ? res.trim() : _(''); | ||
// Without log processing | ||
// With log processing | ||
if (res) { | ||
const processedLog = res.trim().split('\n').map(line => { | ||
const msgMatch = line.match(/msg="(.*?)"/); | ||
if (msgMatch) { | ||
return line.split(']: ')[0] + ']: ' + msgMatch[1]; | ||
} | ||
return line; | ||
}).join('\n'); | ||
|
||
log.value = processedLog; | ||
} else { | ||
log.value = _(''); | ||
} | ||
// With log processing | ||
log.scrollTop = log.scrollHeight; | ||
}) | ||
.catch(err => { | ||
console.error('Error executing logread:', err); | ||
}); | ||
} | ||
}); | ||
|
||
return E( | ||
'div', | ||
{ class: 'cbi-map' }, | ||
E('div', { class: 'cbi-section' }, [ | ||
E('textarea', { | ||
id: 'logfile', | ||
style: 'width: 100% !important; padding: 5px; font-family: monospace', | ||
readonly: 'readonly', | ||
wrap: 'off', | ||
rows: 35, | ||
}), | ||
]) | ||
); | ||
}, | ||
|
||
handleSaveApply: null, | ||
handleSave: null, | ||
handleReset: null, | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.