diff --git a/luci-app-daed/Makefile b/luci-app-daed/Makefile
new file mode 100644
index 000000000..ff102937b
--- /dev/null
+++ b/luci-app-daed/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright (C) 2023 ImmortalWrt.org
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI app for dae dashboard
+LUCI_DEPENDS:=+daed +daed-geoip +daed-geosite
+LUCI_PKGARCH:=all
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js b/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js
new file mode 100644
index 000000000..e4a97947c
--- /dev/null
+++ b/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: Apache-2.0
+
+'use strict';
+'require form';
+'require poll';
+'require rpc';
+'require uci';
+'require view';
+
+var callServiceList = rpc.declare({
+ object: 'service',
+ method: 'list',
+ params: ['name'],
+ expect: { '': {} }
+});
+
+function getServiceStatus() {
+ return L.resolveDefault(callServiceList('daed'), {}).then(function (res) {
+ var isRunning = false;
+ try {
+ isRunning = res['daed']['instances']['daed']['running'];
+ } catch (e) { }
+ return isRunning;
+ });
+}
+
+function renderStatus(isRunning, port) {
+ var spanTemp = '%s %s';
+ var renderHTML;
+ if (isRunning) {
+ var button = String.format(' %s',
+ window.location.hostname, port, _('Open Web Interface'));
+ renderHTML = spanTemp.format('green', _('daed'), _('RUNNING')) + button;
+ } else {
+ renderHTML = spanTemp.format('red', _('daed'), _('NOT RUNNING'));
+ }
+
+ return renderHTML;
+}
+
+return view.extend({
+ load: function() {
+ return Promise.all([
+ uci.load('daed')
+ ]);
+ },
+
+ render: function(data) {
+ var m, s, o;
+ var webport = (uci.get(data[0], 'config', 'address') || '0.0.0.0:2023').split(':').slice(-1)[0];
+
+ m = new form.Map('daed', _('daed'),
+ _('A modern dashboard for dae.'));
+
+ s = m.section(form.TypedSection);
+ s.anonymous = true;
+ s.render = function () {
+ poll.add(function () {
+ return L.resolveDefault(getServiceStatus()).then(function (res) {
+ var view = document.getElementById('service_status');
+ view.innerHTML = renderStatus(res, webport);
+ });
+ });
+
+ return E('div', { class: 'cbi-section', id: 'status_bar' }, [
+ E('p', { id: 'service_status' }, _('Collecting data…'))
+ ]);
+ }
+
+ s = m.section(form.NamedSection, 'config', 'daed');
+
+ o = s.option(form.Flag, 'enabled', _('Enable'));
+ o.default = o.disabled;
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'listen_addr', _('Listening address'));
+ o.datatype = 'ipaddrport(1)';
+ o.default = '0.0.0.0:2023';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'log_maxbackups', _('Max log backups'),
+ _('The maximum number of old log files to retain.'));
+ o.datatype = 'uinteger';
+ o.default = '1';
+
+ o = s.option(form.Value, 'log_maxsize', _('Max log size'),
+ _('The maximum size in megabytes of the log file before it gets rotated.'));
+ o.datatype = 'uinteger';
+ o.default = '5';
+
+ return m.render();
+ }
+});
diff --git a/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js b/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js
new file mode 100644
index 000000000..e3ad038e6
--- /dev/null
+++ b/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: Apache-2.0
+
+'use strict';
+'require dom';
+'require fs';
+'require poll';
+'require view';
+
+return view.extend({
+ render: function() {
+ /* Thanks to luci-app-aria2 */
+ var css = ' \
+ #log_textarea { \
+ text-align: left; \
+ } \
+ #log_textarea pre { \
+ padding: .5rem; \
+ word-break: break-all; \
+ margin: 0; \
+ } \
+ .description { \
+ background-color: #33ccff; \
+ }';
+
+ var log_textarea = E('div', { 'id': 'log_textarea' },
+ E('img', {
+ 'src': L.resource(['icons/loading.gif']),
+ 'alt': _('Loading...'),
+ 'style': 'vertical-align:middle'
+ }, _('Collecting data…'))
+ );
+
+ poll.add(L.bind(function() {
+ return fs.read_direct('/var/log/daed/daed.log', 'text')
+ .then(function(content) {
+ var log = E('pre', { 'wrap': 'pre' }, [
+ content.trim() || _('Log is empty.')
+ ]);
+
+ dom.content(log_textarea, log);
+ }).catch(function(e) {
+ var log;
+
+ if (e.toString().includes('NotFoundError'))
+ log = E('pre', { 'wrap': 'pre' }, [
+ _('Log file does not exist.')
+ ]);
+ else
+ log = E('pre', { 'wrap': 'pre' }, [
+ _('Unknown error: %s').format(e)
+ ]);
+
+ dom.content(log_textarea, log);
+ });
+ }));
+
+ var scrollDownButton = E('button', {
+ 'id': 'scrollDownButton',
+ 'class': 'cbi-button cbi-button-neutral',
+ }, _('Scroll to tail', 'scroll to bottom (the tail) of the log file')
+ );
+ scrollDownButton.addEventListener('click', function() {
+ scrollUpButton.focus();
+ });
+
+ var scrollUpButton = E('button', {
+ 'id' : 'scrollUpButton',
+ 'class': 'cbi-button cbi-button-neutral',
+ }, _('Scroll to head', 'scroll to top (the head) of the log file')
+ );
+ scrollUpButton.addEventListener('click', function() {
+ scrollDownButton.focus();
+ });
+
+ return E([
+ E('style', [ css ]),
+ E('h2', {}, [ _('Log') ]),
+ E('div', {'class': 'cbi-map'}, [
+ E('div', {'style': 'padding-bottom: 20px'}, [scrollDownButton]),
+ E('div', {'class': 'cbi-section'}, [
+ log_textarea,
+ E('div', {'style': 'text-align:right'},
+ E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
+ )
+ ]),
+ E('div', {'style': 'padding-bottom: 20px'}, [scrollUpButton])
+ ])
+ ]);
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/luci-app-daed/po/templates/daed.pot b/luci-app-daed/po/templates/daed.pot
new file mode 100644
index 000000000..4807fbc04
--- /dev/null
+++ b/luci-app-daed/po/templates/daed.pot
@@ -0,0 +1,97 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:53
+msgid "A modern dashboard for dae."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:66
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:30
+msgid "Collecting data…"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:72
+msgid "Enable"
+msgstr ""
+
+#: applications/luci-app-daed/root/usr/share/rpcd/acl.d/luci-app-daed.json:3
+msgid "Grant access to daed configuration"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:76
+msgid "Listening address"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:28
+msgid "Loading..."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:77
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:22
+msgid "Log"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:46
+msgid "Log file does not exist."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:37
+msgid "Log is empty."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:81
+msgid "Max log backups"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:86
+msgid "Max log size"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:35
+msgid "NOT RUNNING"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:32
+msgid "Open Web Interface"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:33
+msgid "RUNNING"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:83
+msgid "Refresh every %s seconds."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:69
+msgctxt "scroll to top (the head) of the log file"
+msgid "Scroll to head"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:60
+msgctxt "scroll to bottom (the tail) of the log file"
+msgid "Scroll to tail"
+msgstr ""
+
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:14
+msgid "Settings"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:82
+msgid "The maximum number of old log files to retain."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:87
+msgid "The maximum size in megabytes of the log file before it gets rotated."
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:50
+msgid "Unknown error: %s"
+msgstr ""
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:33
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:35
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:52
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:3
+msgid "daed"
+msgstr ""
diff --git a/luci-app-daed/po/zh_Hans/daed.po b/luci-app-daed/po/zh_Hans/daed.po
new file mode 100644
index 000000000..c02df9e3c
--- /dev/null
+++ b/luci-app-daed/po/zh_Hans/daed.po
@@ -0,0 +1,104 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh-Hans\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:53
+msgid "A modern dashboard for dae."
+msgstr "dae 现代化控制面板。"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:66
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:30
+msgid "Collecting data…"
+msgstr "收集数据中…"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:72
+msgid "Enable"
+msgstr "启用"
+
+#: applications/luci-app-daed/root/usr/share/rpcd/acl.d/luci-app-daed.json:3
+msgid "Grant access to daed configuration"
+msgstr "授予访问 daed 配置的权限"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:76
+msgid "Listening address"
+msgstr "监听地址"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:28
+msgid "Loading..."
+msgstr "加载中..."
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:77
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:22
+msgid "Log"
+msgstr "日志"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:46
+msgid "Log file does not exist."
+msgstr "日志文件不存在。"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:37
+msgid "Log is empty."
+msgstr "日志为空"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:81
+msgid "Max log backups"
+msgstr "最大日志备份"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:86
+msgid "Max log size"
+msgstr "最大日志大小"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:35
+msgid "NOT RUNNING"
+msgstr "未运行"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:32
+msgid "Open Web Interface"
+msgstr "打开 Web 界面"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:33
+msgid "RUNNING"
+msgstr "运行中"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:83
+msgid "Refresh every %s seconds."
+msgstr "每 %s 秒刷新。"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:69
+msgctxt "scroll to top (the head) of the log file"
+msgid "Scroll to head"
+msgstr "滚动到顶部"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:60
+msgctxt "scroll to bottom (the tail) of the log file"
+msgid "Scroll to tail"
+msgstr "滚动到尾部"
+
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:14
+msgid "Settings"
+msgstr "设置"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:82
+msgid "The maximum number of old log files to retain."
+msgstr "要保留的最大旧日志文件数量。"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:87
+msgid "The maximum size in megabytes of the log file before it gets rotated."
+msgstr "要保留的最大日志大小(单位:MB)。"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/log.js:50
+msgid "Unknown error: %s"
+msgstr "未知错误:%s"
+
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:33
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:35
+#: applications/luci-app-daed/htdocs/luci-static/resources/view/daed/config.js:52
+#: applications/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json:3
+msgid "daed"
+msgstr "daed"
diff --git a/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json b/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json
new file mode 100644
index 000000000..7dd5e2b48
--- /dev/null
+++ b/luci-app-daed/root/usr/share/luci/menu.d/luci-app-daed.json
@@ -0,0 +1,29 @@
+{
+ "admin/services/daed": {
+ "title": "daed",
+ "order": 20,
+ "action": {
+ "type": "firstchild"
+ },
+ "depends": {
+ "acl": [ "luci-app-daed" ],
+ "uci": { "daed": true }
+ }
+ },
+ "admin/services/daed/config": {
+ "title": "Settings",
+ "order": 10,
+ "action": {
+ "type": "view",
+ "path": "daed/config"
+ }
+ },
+ "admin/services/daed/log": {
+ "title": "Log",
+ "order": 20,
+ "action": {
+ "type": "view",
+ "path": "daed/log"
+ }
+ }
+}
diff --git a/luci-app-daed/root/usr/share/rpcd/acl.d/luci-app-daed.json b/luci-app-daed/root/usr/share/rpcd/acl.d/luci-app-daed.json
new file mode 100644
index 000000000..8ec379d9d
--- /dev/null
+++ b/luci-app-daed/root/usr/share/rpcd/acl.d/luci-app-daed.json
@@ -0,0 +1,17 @@
+{
+ "luci-app-daed": {
+ "description": "Grant access to daed configuration",
+ "read": {
+ "file": {
+ "/var/log/daed/daed.log": [ "read" ]
+ },
+ "ubus": {
+ "service": [ "list" ]
+ },
+ "uci": [ "daed" ]
+ },
+ "write": {
+ "uci": [ "daed" ]
+ }
+ }
+}