diff --git a/usr/lib/dooke/dooke.py b/usr/lib/dooke/dooke.py index 555b584..20d03ce 100755 --- a/usr/lib/dooke/dooke.py +++ b/usr/lib/dooke/dooke.py @@ -14,11 +14,6 @@ __dir__ = os.path.dirname(os.path.realpath(__file__)) web_root = os.path.join(__dir__, './webapp') home_dir = os.getenv('HOME') -config_dir = os.path.join(home_dir, '.config/dooke') - - -if not os.path.isdir(config_dir): - os.mkdir(config_dir) class Application(Gtk.Application): diff --git a/usr/lib/dooke/webapp/app.js b/usr/lib/dooke/webapp/app.js index 7ff2e36..aefcdcd 100644 --- a/usr/lib/dooke/webapp/app.js +++ b/usr/lib/dooke/webapp/app.js @@ -6,10 +6,21 @@ import 'es.shim' import { html, css, Component } from 'wkit' -import { createApp, createRouter } from 'wkitd' +import { createApp } from 'wkitd' +import 'ui/icon/index.js' +import 'ui/scroll/index.js' +import 'ui/layer/index.js' +import 'ui/space/index.js' +import 'ui/form/input.js' +import 'ui/form/switch.js' +import 'ui/form/button.js' +import 'ui/form/link.js' + +import router from './router.js' import store from './store.js' -import './components/home.js' + +import '../components/sidebar.js' import { noop } from './utils/index.js' @@ -26,6 +37,11 @@ createApp({ width: 100%; height: 100vh; } + + router-view { + flex: 1; + height: 100; + } ` ], methods: { @@ -34,8 +50,18 @@ createApp({ } }, render() { - return html` ` + return html` + + + + + + ` } }) + .use(router) .use(store) .mount() diff --git a/usr/lib/dooke/webapp/components/home.js b/usr/lib/dooke/webapp/components/home.js deleted file mode 100644 index 68fbf28..0000000 --- a/usr/lib/dooke/webapp/components/home.js +++ /dev/null @@ -1,262 +0,0 @@ -/** - * {} - * @author yutent - * @date 2023/08/08 18:19:17 - */ -import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit' - -import 'ui/icon/index.js' -import 'ui/scroll/index.js' -import 'ui/layer/index.js' -import 'ui/space/index.js' -import 'ui/form/input.js' -import 'ui/form/switch.js' -import 'ui/form/button.js' -import 'ui/form/link.js' - -import './sidebar.js' -import './permission.js' -import './records.js' - -import { checkPermission, getHistory, saveHosts, noop } from '../utils/index.js' - -const HOST_DATA = await getHistory() - -class Home extends Component { - static props = { - editDomain: '', // 当前临时要编辑的域名, 即右键菜单选择到的 - permissionShow: false - } - - static styles = [ - css` - :host { - flex: 1; - display: flex; - width: 100%; - height: 100%; - padding: 32px; - color: var(--color-dark-1); - background: #f0f0f0; - } - .visible { - display: block; - } - - ul li { - list-style: none; - } - - .noselect { - -webkit-touch-callout: none; - -webkit-user-select: none; - user-select: none; - } - `, - - css` - .main { - flex: 1; - display: flex; - flex-direction: column; - } - .main .toolbar { - display: flex; - align-items: center; - justify-content: space-between; - height: 40px; - padding: 0 15px; - border-bottom: 1px solid var(--color-plain-3); - background: var(--color-plain-1); - } - .main .list { - overflow: hidden; - flex: 1; - } - `, - - css` - .context-menu { - display: flex; - flex-direction: column; - width: 100px; - padding: 5px 0; - background: #fff; - } - - .context-menu .item { - height: 30px; - line-height: 30px; - padding: 0 15px; - cursor: pointer; - } - - .context-menu .item :hover { - background: #f2f5fc; - } - ` - ] - - async mounted() { - let writable = await checkPermission() - - if (writable) { - this.$store.HOST_DATA = HOST_DATA - this.$store.domains = Object.keys(HOST_DATA) - this.$refs.domain.mounted() - outsideClick(this.$refs.context, _ => this.$refs.context.close()) - } else { - this.permissionShow = true - } - } - - addRecord() { - if (this.$store.activeDomain) { - this.$store.records.push({ - record: '', - value: '', - enabled: true, - remark: '' - }) - nextTick(_ => (this.$refs.list.scrollTop = 1e6)) - } else { - layer.toast('请先选择域名', 'warn') - } - } - - showMenu(ev) { - this.$refs.context.close() - var { pageX, pageY } = ev - if (pageY + 70 > 600) { - pageY -= 70 - } - - var elem = ev.target - - if (elem.tagName !== 'LI') { - elem = elem.parentNode - } - this.editDomain = elem.dataset.name - setTimeout(_ => { - this.$refs.context.moveTo({ left: pageX + 'px', top: pageY + 'px' }) - this.$refs.context.show() - }) - } - - confirmAction(ev) { - this.$refs.context.close() - if (ev.target.tagName === 'LI') { - let act = ev.target.dataset.act - let { HOST_DATA, records, domains } = this.$store - let idx = domains.indexOf(this.editDomain) - - if (act === 'del') { - layer - .confirm(`是否要删除域名「${this.editDomain}」?`) - .then(res => { - if (this.editDomain === this.$store.activeDomain) { - if (records.length) { - return layer.toast( - '该域名下有主机记录, 请先删除主机记录后再删除域名', - 'error' - ) - } - } else { - if (HOST_DATA[this.editDomain].length > 0) { - return layer.toast( - '该域名下有主机记录, 请先删除主机记录后再删除域名', - 'error' - ) - } - } - delete HOST_DATA[this.editDomain] - - domains.splice(idx, 1) - - this.editDomain = '' - this.$store.records = [] - this.$store.activeDomain = domains[0] - this.$refs.domain.mounted() - this.save() - }) - .catch(noop) - } else if (act === 'edit') { - layer - .prompt( - `请输入新的名字「${this.editDomain}」`, - this.editDomain, - (val, done) => { - if (val === this.editDomain || HOST_DATA[val]) { - return layer.toast(`${val} 域名没有变化, 或已经存在`) - } - - if ( - val === 'localhost' || - val === 'local' || - /^[\w.]+\.[a-z]+$/.test(val) - ) { - done() - } else { - layer.toast('域名格式错误', 'error') - } - } - ) - .then(val => { - domains[idx] = val - HOST_DATA[val] = HOST_DATA[this.editDomain] - delete HOST_DATA[this.editDomain] - - this.$store.activeDomain = val - this.editDomain = '' - this.$refs.domain.mounted() - this.save() - }) - .catch(noop) - } - } - } - - save() { - let { HOST_DATA, activeDomain, records } = this.$store - if (activeDomain) { - HOST_DATA[activeDomain] = records.filter(it => it.record && it.value) - } - saveHosts(HOST_DATA) - layer.toast('保存成功', 'success') - } - - render() { - return html` - (this.$refs.list.scrollTop = 0)} - @show-menu=${ev => this.showMenu(ev.event)} - @save=${this.save} - > - -
-
- 新增记录 - 保存 -
- - -
- - - - - - - ` - } -} - -Home.reg('home') diff --git a/usr/lib/dooke/webapp/components/permission.js b/usr/lib/dooke/webapp/components/permission.js deleted file mode 100644 index 8393252..0000000 --- a/usr/lib/dooke/webapp/components/permission.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * {} - * @author yutent - * @date 2023/08/08 18:19:17 - */ -import { html, css, Component } from 'wkit' - -import { checkPermission } from '../utils/index.js' - -const tips_header = `/************************************************************/ - * hosts文件没有写权限 * -/************************************************************/ -` - -class Permission extends Component { - static styles = css` - :host { - display: none; - } - .noselect { - -webkit-touch-callout: none; - -webkit-user-select: none; - user-select: none; - } - .permission-error { - position: fixed; - left: 0; - top: 0; - z-index: 1024; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - height: 100%; - padding: 24px 52px; - line-height: 1.5; - background: rgba(255, 233, 233, 0.95); - -webkit-backdrop-filter: blur(5px); - } - - pre { - font-family: 'Courier New', Courier, monospace; - font-size: 14px; - color: var(--color-red-1); - } - - fieldset { - width: 600px; - padding: 0 30px 30px; - border: 1px solid var(--color-orange-1); - border-radius: 8px; - } - - legend { - padding: 0 10px; - font-size: 16px; - } - - dt { - margin-top: 20px; - font-weight: bold; - } - code { - display: block; - padding: 8px 10px; - margin-top: 8px; - border-left: 3px solid var(--color-plain-3); - background: rgba(255, 255, 255, 0.8); - font-family: 'Courier New', Courier, monospace; - } - ` - - async check() { - let writable = await checkPermission() - if (writable) { - location.reload() - } else { - layer.toast('hosts文件没有写权限, 请按提示修改', 'error') - } - } - - render() { - return html` -
-
${tips_header}
-
- 操作指引 -
-
MacOS用户
-
打开终端, 执行以下命令
-
sudo chown $USER:admin /etc/hosts
- -
Linux用户
-
打开终端, 执行以下命令
-
sudo chown $USER: /etc/hosts
- -
完成之后
-
点击下面的按钮重新检测.
-
- 权限检测 -
-
-
-
- ` - } -} - -Permission.reg('permission') diff --git a/usr/lib/dooke/webapp/components/sidebar.js b/usr/lib/dooke/webapp/components/sidebar.js index 08783af..0663c18 100644 --- a/usr/lib/dooke/webapp/components/sidebar.js +++ b/usr/lib/dooke/webapp/components/sidebar.js @@ -15,7 +15,7 @@ class Sidebar extends Component { flex-direction: column; width: 180px; height: 100vh; - background: var(--color-plain-2); + background: var(--color-plain-1); } .noselect { -webkit-touch-callout: none; @@ -23,26 +23,28 @@ class Sidebar extends Component { user-select: none; } - .domain-list { + .navibar { overflow: hidden; flex: 1; + display: flex; + flex-direction: column; width: 100%; + gap: 8px; + padding: 16px; } - .domain-list .item { + .navibar .item { display: flex; - justify-content: flex-end; + justify-content: center; align-items: center; height: 40px; padding: 0 12px; + gap: 12px; + border-radius: 20px; cursor: pointer; transition: background 0.15s ease-in-out; - } - .item wc-icon { - --wc-icon-size: 12px; - margin-left: 8px; - color: var(--color-grey-2); + --wc-icon-size: 16px; } .item:hover, @@ -50,114 +52,43 @@ class Sidebar extends Component { background: #fff; } .item.active { - border-right: 2px solid var(--color-orange-1); - color: var(--color-orange-1); - } - .item.active wc-icon { - color: var(--color-orange-1); - } - .item.blank { - justify-content: center; - cursor: default; - } - - .item.blank:hover { - background: none; - } - - .action { - flex-shrink: 0; - display: flex; - align-items: center; - height: 50px; - padding: 0 10px; + color: var(--color-orange-3); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.05); } ` - addDomain() { - layer - .prompt('请输入根域名', function (val, done) { - if ( - val === 'localhost' || - val === 'local' || - /^[\w.\-]+\.[a-z]+$/.test(val) - ) { - done() - } else { - layer.toast('域名格式错误', 'error') - } - }) - .then(val => { - let { HOST_DATA, records } = this.$store - this.$store.domains.push(val) - HOST_DATA[val] = [] - if (!this.$store.activeDomain) { - this.toggleDomain(null, val) - } - this.$emit('save') - }) - .catch(noop) - } - - toggleDomain(ev, name) { - let { HOST_DATA, records } = this.$store - name = name ?? ev.currentTarget.dataset.name - this.$store.activeDomain = name - - this.$store.records = records = (HOST_DATA[name] || []).sort((a, b) => - a.record.localeCompare(b.record) - ) - - let tmp_records = Object.create(null) - for (let it of records) { - if (tmp_records[it.record]) { - tmp_records[it.record].push(it) - } else { - tmp_records[it.record] = [it] - } + switchNavi(id) { + let path = '/' + id + this.$store.navi = id + if (id === 'containers') { + path = '/' } - this.$store.tmp_records = tmp_records - - document.title = `伪域名解析 ${name} ` - localStorage.setItem('last_domain', name) - nextTick(() => { - this.$emit('toggle-domain') - }) + this.$router.push(path) + localStorage.setItem('last_navi', id) } mounted() { - this.toggleDomain(null, this.$store.activeDomain) + this.switchNavi(this.$store.navi) } render() { return html` - -
    this.$emit('show-menu', { event: ev })} - > - ${this.$store.domains.map( - it => html` -
  • - ${it} - -
  • - ` - )} - ${this.$store.domains.length < 1 - ? html`
  • 没有域名
  • ` - : ''} -
-
-
- -
+ + ${this.$store.menus.map( + it => html` +
  • this.switchNavi(it.id)} + > + + ${it.name} +
  • + ` + )} +
    ` } } diff --git a/usr/lib/dooke/webapp/router.js b/usr/lib/dooke/webapp/router.js new file mode 100644 index 0000000..8306cf0 --- /dev/null +++ b/usr/lib/dooke/webapp/router.js @@ -0,0 +1,29 @@ +/** + * {} + * @author yutent + * @date 2024/01/11 17:42:40 + */ + +import { createRouter, createWebHistory } from 'wkitd' + +import './views/home.js' +import './views/images.js' +import './views/volumes.js' + +export default createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'wc-containers' + }, + { + path: '/images', + name: 'wc-images' + }, + { + path: '/volumes', + name: 'wc-volumes' + } + ] +}) diff --git a/usr/lib/dooke/webapp/store.js b/usr/lib/dooke/webapp/store.js index 5ff3f17..2dadfd9 100644 --- a/usr/lib/dooke/webapp/store.js +++ b/usr/lib/dooke/webapp/store.js @@ -7,9 +7,22 @@ import { createStore } from 'wkitd' export default createStore({ - HOST_DATA: {}, - activeDomain: localStorage.getItem('last_domain') || '', //当前选中的域名 - domains: [], - records: [], - tmp_records: [] + navi: localStorage.getItem('last_navi') || 'containers', + menus: [ + { + id: 'containers', + name: '容器管理', + icon: 'layout' + }, + { + id: 'images', + name: '镜像管理', + icon: 'menu' + }, + { + id: 'volumes', + name: '磁盘管理', + icon: 'pie' + } + ] }) diff --git a/usr/lib/dooke/webapp/utils/index.js b/usr/lib/dooke/webapp/utils/index.js index 3e3fc21..57c34e8 100644 --- a/usr/lib/dooke/webapp/utils/index.js +++ b/usr/lib/dooke/webapp/utils/index.js @@ -6,95 +6,10 @@ import { nextTick } from 'wkit' -const APP_CONFIG_DIR = `${native.env.CONFIG_DIR}/hosts-switch` -const HOST_FILE = `${APP_CONFIG_DIR}/host.cache` -const LOCK_FILE = `${APP_CONFIG_DIR}/lock` - export function noop() {} -export function checkPermission() { - return native.fs.access('/etc/hosts', 'a+') -} +export function checkPermission() {} -export async function getHistory() { - if (await native.fs.isfile(LOCK_FILE)) { - let cache = await native.fs.read(HOST_FILE) - return JSON.parse(cache) - } +export async function getHistory() {} - let cache = await native.fs.read('/etc/hosts') - let records = cache.split(/[\n\r]+/) - let list = [] - let dict = {} - - records.forEach(str => { - str = str.trim() - let matches = str.match(/^(#*?)\s*(\d+\.\d+\.\d+\.\d+)\s+(.*)/) - - if (matches) { - let names = matches[3].split(/\s+/).map(it => it.trim()) - let name - while ((name = names.pop())) { - list.push({ ip: matches[2], enabled: !matches[1], name }) - } - } - }) - records = null - - list.forEach(it => { - it.name = it.name.split('.') - let domain = it.name.splice(-2, 2).join('.') - if (domain === 'com.cn' || domain === 'org.cn' || domain === 'net.cn') { - domain = it.name.pop() + '.' + domain - } - - if (dict[domain]) { - dict[domain].push({ - value: it.ip, - enabled: it.enabled, - record: it.name.join('.') || '@', - remark: '' - }) - } else { - dict[domain] = [ - { - value: it.ip, - enabled: it.enabled, - record: it.name.join('.') || '@', - remark: '' - } - ] - } - }) - list = null - try { - await native.fs.write(HOST_FILE, JSON.stringify(dict)) - await native.fs.write(LOCK_FILE, '') - } catch (err) {} - return dict -} - -export function saveHosts(dict) { - nextTick(async () => { - var txt = '' - for (let k in dict) { - for (let it of dict[k]) { - if (it.enabled) { - var name = it.record === '@' ? '' : it.record - if (name) { - name += '.' - } - txt += `${it.value.padEnd(15, ' ')} ${name + k}\n` - } - } - txt += '\n' - } - - try { - await native.fs.write(HOST_FILE, JSON.stringify(dict)) - await native.fs.write('/etc/hosts', txt) - } catch (err) { - layer.alert(err) - } - }) -} +export function saveHosts(dict) {} diff --git a/usr/lib/dooke/webapp/views/home.js b/usr/lib/dooke/webapp/views/home.js new file mode 100644 index 0000000..7b7e34f --- /dev/null +++ b/usr/lib/dooke/webapp/views/home.js @@ -0,0 +1,133 @@ +/** + * {} + * @author yutent + * @date 2023/08/08 18:19:17 + */ +import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit' + +// import '../components/records.js' + +import { noop } from '../utils/index.js' + +class Home extends Component { + static props = {} + + static styles = [ + css` + :host { + display: block; + width: 100%; + height: 100%; + color: var(--color-dark-1); + background: #fff; + } + .visible { + display: block; + } + + ul li { + list-style: none; + } + + .noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + } + `, + + css` + .main { + display: flex; + flex-direction: column; + padding: 16px; + } + .main .toolbar { + display: flex; + align-items: center; + justify-content: space-between; + height: 48px; + border-bottom: 1px solid var(--color-plain-3); + } + .main .list { + overflow: hidden; + flex: 1; + } + `, + + css` + .context-menu { + display: flex; + flex-direction: column; + width: 100px; + padding: 5px 0; + background: #fff; + } + + .context-menu .item { + height: 30px; + line-height: 30px; + padding: 0 15px; + cursor: pointer; + } + + .context-menu .item :hover { + background: #f2f5fc; + } + ` + ] + + async mounted() { + // outsideClick(this.$refs.context, _ => this.$refs.context.close()) + } + + showMenu(ev) { + this.$refs.context.close() + var { pageX, pageY } = ev + if (pageY + 70 > 600) { + pageY -= 70 + } + + var elem = ev.target + + if (elem.tagName !== 'LI') { + elem = elem.parentNode + } + this.editDomain = elem.dataset.name + setTimeout(_ => { + this.$refs.context.moveTo({ left: pageX + 'px', top: pageY + 'px' }) + this.$refs.context.show() + }) + } + + confirmAction(ev) { + this.$refs.context.close() + if (ev.target.tagName === 'LI') { + let act = ev.target.dataset.act + if (act === 'del') { + } else if (act === 'edit') { + } + } + } + + save() { + let { HOST_DATA, activeDomain, records } = this.$store + if (activeDomain) { + HOST_DATA[activeDomain] = records.filter(it => it.record && it.value) + } + saveHosts(HOST_DATA) + layer.toast('保存成功', 'success') + } + + render() { + return html` +
    +
    + +
    +
    + ` + } +} + +Home.reg('containers') diff --git a/usr/lib/dooke/webapp/views/images.js b/usr/lib/dooke/webapp/views/images.js new file mode 100644 index 0000000..e69de29 diff --git a/usr/lib/dooke/webapp/views/volumes.js b/usr/lib/dooke/webapp/views/volumes.js new file mode 100644 index 0000000..e69de29