界面更新
parent
85e68fc231
commit
ee6dd0f1ad
|
@ -3,6 +3,13 @@
|
||||||
# @date 2024/01/15 15:38:34
|
# @date 2024/01/15 15:38:34
|
||||||
|
|
||||||
import subprocess, json
|
import subprocess, json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def toISOTime(time_str):
|
||||||
|
_time = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S %z %Z')
|
||||||
|
return _time.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Docker:
|
class Docker:
|
||||||
|
|
||||||
|
@ -17,16 +24,31 @@ class Docker:
|
||||||
out = out.stdout.decode().strip().split("\n")
|
out = out.stdout.decode().strip().split("\n")
|
||||||
out = [json.loads(it) for it in out]
|
out = [json.loads(it) for it in out]
|
||||||
|
|
||||||
# print(out)
|
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
"id": it['ID'],
|
"id": it['ID'],
|
||||||
"name": it['Names'],
|
"name": it['Names'],
|
||||||
"image": it['Image'],
|
"image": it['Image'],
|
||||||
|
"cmd":it['Command'],
|
||||||
"state": it['State'],
|
"state": it['State'],
|
||||||
'status': it['Status'],
|
'status': it['Status'],
|
||||||
'port': it['Ports'].split(', ')[0],
|
'port': it['Ports'].split(', ')[0],
|
||||||
'created': it['CreatedAt'],
|
'created': toISOTime(it['CreatedAt']),
|
||||||
'last': it['RunningFor'],
|
'last': it['RunningFor'],
|
||||||
} for it in out]
|
} for it in out]
|
||||||
|
|
||||||
|
|
||||||
|
def images(self):
|
||||||
|
cmd = 'docker images --format=json'
|
||||||
|
|
||||||
|
out = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
out = out.stdout.decode().strip().split("\n")
|
||||||
|
out = [json.loads(it) for it in out]
|
||||||
|
|
||||||
|
return [{
|
||||||
|
"id": it['ID'],
|
||||||
|
"name": it['Repository'],
|
||||||
|
"tag": it['Tag'],
|
||||||
|
'size': it['Size'],
|
||||||
|
'created': toISOTime(it['CreatedAt']),
|
||||||
|
} for it in out]
|
|
@ -54,9 +54,13 @@ class Application(Gtk.Application):
|
||||||
action = params['action']
|
action = params['action']
|
||||||
|
|
||||||
match action:
|
match action:
|
||||||
case 'all-containers':
|
case 'containers':
|
||||||
output = client.containers(all = True)
|
all = params['all'] or False
|
||||||
# print(output)
|
output = client.containers(all)
|
||||||
|
|
||||||
|
case 'images':
|
||||||
|
output = client.images()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (_error, output)
|
return (_error, output)
|
||||||
|
|
|
@ -13,11 +13,12 @@ import 'ui/base/button.js'
|
||||||
import 'ui/base/space.js'
|
import 'ui/base/space.js'
|
||||||
import 'ui/base/link.js'
|
import 'ui/base/link.js'
|
||||||
import 'ui/other/scroll.js'
|
import 'ui/other/scroll.js'
|
||||||
import 'ui/modal/layer.js'
|
|
||||||
import 'ui/form/input.js'
|
import 'ui/form/input.js'
|
||||||
import 'ui/form/switch.js'
|
import 'ui/form/switch.js'
|
||||||
|
import 'ui/modal/layer.js'
|
||||||
import 'ui/modal/popconfirm.js'
|
import 'ui/modal/popconfirm.js'
|
||||||
import 'ui/modal/tooltip.js'
|
import 'ui/modal/tooltip.js'
|
||||||
|
import 'ui/views/time.js'
|
||||||
|
|
||||||
import router from './router.js'
|
import router from './router.js'
|
||||||
import store from './store.js'
|
import store from './store.js'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
import{css as e,html as a,Component as t}from"wkit";class s extends t{static styles=e`:host{display:block}.container{--gaps: var(--wc-space-gap, 12px);display:flex;flex-wrap:wrap;align-items:center;width:100%;padding:calc(var(--gaps)/2) 0;gap:var(--gaps)}:host([vertical]) .container{flex-direction:column}:host([justify]) .container{justify-content:space-between}`;render(){return a`<div class="container"><slot></slot></div>`}}s.reg("space");
|
import{css as e,html as t,Component as a}from"wkit";class s extends a{static styles=e`:host{display:block}.container{--gaps: var(--wc-space-gap, 12px);display:flex;flex-wrap:wrap;align-items:center;width:100%;height:100%;padding:calc(var(--gaps)/2) 0;gap:var(--gaps)}:host([vertical]) .container{flex-direction:column}:host([justify]) .container{justify-content:space-between}`;render(){return t`<div class="container"><slot></slot></div>`}}s.reg("space");
|
||||||
|
|
|
@ -8,6 +8,10 @@ import { nextTick } from 'wkit'
|
||||||
|
|
||||||
export function noop() {}
|
export function noop() {}
|
||||||
|
|
||||||
export function getAllContainers() {
|
export function getContainers(all = true) {
|
||||||
return native.handler('docker', { action: 'all-containers' })
|
return native.handler('docker', { action: 'containers', all })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImages() {
|
||||||
|
return native.handler('docker', { action: 'images' })
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit'
|
import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit'
|
||||||
|
|
||||||
// import '../components/records.js'
|
import { noop, getContainers } from '../utils/index.js'
|
||||||
|
|
||||||
import { noop, getAllContainers } from '../utils/index.js'
|
|
||||||
|
|
||||||
class Home extends Component {
|
class Home extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
|
@ -42,29 +40,38 @@ class Home extends Component {
|
||||||
.main {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
.main .toolbar {
|
.main .toolbar {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-bottom: 1px solid var(--color-plain-3);
|
border-bottom: 1px solid var(--color-plain-3);
|
||||||
}
|
}
|
||||||
.main .list {
|
.main .list {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
gap: 12px;
|
padding: 0 12px;
|
||||||
|
line-height: 1.25;
|
||||||
|
border-radius: 24px;
|
||||||
|
|
||||||
--wc-icon-size: 16px;
|
--wc-icon-size: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -95,72 +102,70 @@ class Home extends Component {
|
||||||
`
|
`
|
||||||
]
|
]
|
||||||
|
|
||||||
async mounted() {
|
#input = ''
|
||||||
// outsideClick(this.$refs.context, _ => this.$refs.context.close())
|
#all = true
|
||||||
let list = await getAllContainers()
|
|
||||||
|
mounted() {
|
||||||
|
this.#all = +localStorage.getItem('container_mode') === 1
|
||||||
|
console.log('<><><>', this.#all)
|
||||||
|
this.#fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #fetch() {
|
||||||
|
let list = await getContainers(this.#all)
|
||||||
|
|
||||||
console.log(list)
|
|
||||||
list.forEach(it => {
|
list.forEach(it => {
|
||||||
it.port = it.port.replace('0.0.0.0:', '').replace(':::', '')
|
it.port = it.port.replace('0.0.0.0:', '').replace(':::', '')
|
||||||
})
|
})
|
||||||
this.containers = list
|
this.containers = list
|
||||||
}
|
}
|
||||||
|
|
||||||
showMenu(ev) {
|
async #filter(ev) {
|
||||||
this.$refs.context.close()
|
let val = ev.currentTarget.value
|
||||||
var { pageX, pageY } = ev
|
if (ev.type === 'input') {
|
||||||
if (pageY + 70 > 600) {
|
let txt = val.trim()
|
||||||
pageY -= 70
|
if (this.#input === txt) {
|
||||||
}
|
return
|
||||||
|
|
||||||
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') {
|
|
||||||
}
|
}
|
||||||
|
this.#input = txt
|
||||||
|
} else {
|
||||||
|
this.#all = val
|
||||||
|
localStorage.setItem('container_mode', val ^ 0)
|
||||||
|
await this.#fetch()
|
||||||
}
|
}
|
||||||
}
|
this.$requestUpdate()
|
||||||
|
|
||||||
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() {
|
render() {
|
||||||
|
let list = this.containers
|
||||||
|
let txt = this.#input
|
||||||
|
|
||||||
|
if (txt) {
|
||||||
|
list = list.filter(it => it.name.includes(txt))
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<main class="main noselect">
|
<main class="main noselect">
|
||||||
<header class="toolbar">
|
<wc-space class="toolbar">
|
||||||
<wc-input size="small" round placeholder="搜索容器"></wc-input>
|
<wc-input
|
||||||
</header>
|
size="small"
|
||||||
|
round
|
||||||
|
placeholder="搜索容器"
|
||||||
|
@input=${this.#filter}
|
||||||
|
></wc-input>
|
||||||
|
<wc-switch :value=${this.#all} @change=${this.#filter}
|
||||||
|
>显示非运行中的容器</wc-switch
|
||||||
|
>
|
||||||
|
</wc-space>
|
||||||
|
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
${this.containers.map(
|
${list.map(
|
||||||
it => html`
|
it => html`
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<section class="field flex-2 name">
|
<section class="field flex-2 name">
|
||||||
<wc-tooltip title=${it.name}>
|
<wc-tooltip title=${it.name}>
|
||||||
<span class="text-ell">${it.name}</span>
|
<span class="text-ell">${it.name}</span>
|
||||||
</wc-tooltip>
|
</wc-tooltip>
|
||||||
|
<code>${it.id}</code>
|
||||||
<span>${it.id}</span>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="field flex-2">
|
<section class="field flex-2">
|
||||||
<wc-tooltip title=${it.image}>
|
<wc-tooltip title=${it.image}>
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* {}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2023/08/08 18:19:17
|
||||||
|
*/
|
||||||
|
import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit'
|
||||||
|
|
||||||
|
import { noop, getImages } from '../utils/index.js'
|
||||||
|
|
||||||
|
class Images extends Component {
|
||||||
|
static props = {
|
||||||
|
images: []
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.main .toolbar {
|
||||||
|
height: 48px;
|
||||||
|
border-bottom: 1px solid var(--color-plain-3);
|
||||||
|
}
|
||||||
|
.main .list {
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: 1.25;
|
||||||
|
border-radius: 24px;
|
||||||
|
|
||||||
|
--wc-icon-size: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.flex-2 {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-ell {
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: var(--color-teal-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
]
|
||||||
|
|
||||||
|
#input = ''
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
let list = await getImages()
|
||||||
|
|
||||||
|
this.images = list
|
||||||
|
}
|
||||||
|
|
||||||
|
#filter(ev) {
|
||||||
|
let txt = ev.currentTarget.value.trim()
|
||||||
|
if (this.#input === txt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.#input = txt
|
||||||
|
this.$requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let list = this.images
|
||||||
|
let txt = this.#input
|
||||||
|
if (txt) {
|
||||||
|
list = list.filter(it => it.name.includes(txt))
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<main class="main noselect">
|
||||||
|
<wc-space class="toolbar">
|
||||||
|
<wc-input
|
||||||
|
size="small"
|
||||||
|
round
|
||||||
|
placeholder="搜索镜像"
|
||||||
|
@input=${this.#filter}
|
||||||
|
></wc-input>
|
||||||
|
</wc-space>
|
||||||
|
|
||||||
|
<ul class="list">
|
||||||
|
${list.map(
|
||||||
|
it => html`
|
||||||
|
<li class="item">
|
||||||
|
<section class="field flex-2 name">
|
||||||
|
<span class="text-ell">${it.name}</span>
|
||||||
|
<code>${it.id}</code>
|
||||||
|
</section>
|
||||||
|
<section class="field">${it.tag}</section>
|
||||||
|
<section class="field center">${it.size}</section>
|
||||||
|
<section class="field center">
|
||||||
|
<wc-time stamp=${new Date(it.created).getTime()}></wc-time>
|
||||||
|
</section>
|
||||||
|
<section class="field center">
|
||||||
|
<wc-icon name="trash"></wc-icon>
|
||||||
|
</section>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Images.reg('images')
|
Loading…
Reference in New Issue