更新UI和增加删除操作

master
yutent 2024-01-17 18:35:32 +08:00
parent ee6dd0f1ad
commit 7131c4067e
9 changed files with 163 additions and 66 deletions

View File

@ -51,4 +51,13 @@ class Docker:
"tag": it['Tag'], "tag": it['Tag'],
'size': it['Size'], 'size': it['Size'],
'created': toISOTime(it['CreatedAt']), 'created': toISOTime(it['CreatedAt']),
} for it in out] } for it in out]
def rm(self, id = ''):
cmd = 'docker rm ' + id
out = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)
def rmi(self, id = ''):
cmd = 'docker rmi ' + id
out = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)

View File

@ -7,7 +7,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
class Window(Gtk.Window): class Window(Gtk.Window):
def __init__(self, title = 'Untitled window', width = 960, height = 640): def __init__(self, title = 'Untitled window', width = 896, height = 576):
Gtk.Window.__init__(self, title = title) Gtk.Window.__init__(self, title = title)
self.set_default_size(width, height) self.set_default_size(width, height)
self.resize(width, height) self.resize(width, height)

View File

@ -60,6 +60,12 @@ class Application(Gtk.Application):
case 'images': case 'images':
output = client.images() output = client.images()
case 'rm':
client.rm(params.get('id'))
case 'rmi':
client.rmi(params.get('id'))

View File

@ -52,7 +52,7 @@ class Sidebar extends Component {
background: #fff; background: #fff;
} }
.item.active { .item.active {
color: var(--color-orange-3); color: var(--color-blue-3);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.05); box-shadow: 0 0 6px rgba(0, 0, 0, 0.05);
} }
` `

View File

@ -1,4 +1,4 @@
import{css as p,html as c,Component as l,bind as a,unbind as d,styleMap as f,offset as h,outsideClick as m,clearOutsideClick as x}from"wkit";import"../base/button.js";const w="\u8BF7\u786E\u8BA4\u4F60\u7684\u64CD\u4F5C!";class u extends l{static props={title:"str!",confirmButtonType:"str!primary"};static styles=[p`:host{display:inline-flex}.container{position:relative}.tooltip{display:none;position:fixed;z-index:9;justify-content:center;align-items:center;max-width:260px;min-width:32px;padding:8px 12px 12px;border-radius:3px;font-size:var(--wc-popconfirm-font, 14px);background:var(--wc-popconfirm-background, #fff);color:var(--wc-popconfirm-color, var(--color-dark-1));box-shadow:0 0 3px var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.3));word-break:break-all;-webkit-user-select:none;user-select:none}.tooltip::after{position:absolute;display:block;width:8px;height:8px;border-radius:2px;background:var(--wc-popconfirm-background, #fff);content:"";transform:rotate(45deg);box-shadow:-1px -1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-top]::after{right:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-bottom]::after{right:16px;top:-4px}.tooltip[placement=right-top]::after{left:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=right-bottom]::after{left:16px;top:-4px}`,p`.title{display:flex;align-items:center;gap:6px;padding:8px 0;--wc-icon-size: 16px}.title wc-icon{flex-shrink:0;color:var(--wc-popconfirm-icon-color, var(--color-red-1))}:host([hide-icon]) .title wc-icon{display:none}.actions{display:flex;justify-content:flex-end;gap:4px;margin-top:8px}.actions wc-button{min-width:40px;height:20px;font-size:12px}.actions wc-button:first-child{--wc-button-border-color: none}`];#t=!1;#i(s){let{left:t,top:i}=h(this),e="left",o={display:"block"},r=document.documentElement.scrollTop;if(!this.#t){if(t<260||t>260&&window.innerWidth-t>260)e="right",o.left=t+"px";else{let n=window.innerWidth-t+this.clientWidth;o.right=n+"px"}if(i<160)i+=8+this.clientHeight,e+="-bottom",o.top=i+"px";else{let n=window.innerHeight-i+8;e+="-top",o.bottom=n+"px",r>0&&(o.transform=`translateY(-${r}px)`)}this.#t=!0,this.$refs.tips.setAttribute("placement",e),this.$refs.tips.style.cssText=f(o),this.$refs.tips.$animate()}}#o(){this.#t&&(this.#t=!1,this.$refs.tips.$animate(!0))}#e(){this.$emit("confirm"),this.#o()}mounted(){this._outFn=m(this,s=>this.#o()),this._scrollFn=a(document,"scroll",s=>{if(this.#t){let t=document.documentElement.scrollTop;this.$refs.tips.style.transform=`translateY(-${t}px)`}})}unmounted(){d(document,"scroll",this._scrollFn),x(this._outFn)}render(){return c` import{css as p,html as c,Component as l,bind as a,unbind as d,styleMap as f,offset as h,outsideClick as m,clearOutsideClick as x}from"wkit";import"../base/button.js";const w="\u8BF7\u786E\u8BA4\u4F60\u7684\u64CD\u4F5C!";class u extends l{static props={title:"str!",confirmButtonType:"str!primary"};static styles=[p`:host{display:inline-flex}.container{position:relative}.tooltip{display:none;position:fixed;z-index:9;justify-content:center;align-items:center;max-width:260px;min-width:32px;padding:8px 12px 12px;border-radius:3px;font-size:var(--wc-popconfirm-font, 14px);background:var(--wc-popconfirm-background, #fff);color:var(--wc-popconfirm-color, var(--color-dark-1));box-shadow:0 0 3px var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.3));word-break:break-all;-webkit-user-select:none;user-select:none}.tooltip::after{position:absolute;display:block;width:8px;height:8px;border-radius:2px;background:var(--wc-popconfirm-background, #fff);content:"";transform:rotate(45deg);box-shadow:-1px -1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-top]::after{right:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-bottom]::after{right:16px;top:-4px}.tooltip[placement=right-top]::after{left:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-popconfirm-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=right-bottom]::after{left:16px;top:-4px}`,p`.title{display:flex;align-items:center;gap:6px;padding:8px 0;--wc-icon-size: 16px}.title wc-icon{flex-shrink:0;color:var(--wc-popconfirm-icon-color, var(--color-red-1))}:host([hide-icon]) .title wc-icon{display:none}.actions{display:flex;justify-content:flex-end;gap:4px;margin-top:8px}.actions wc-button{min-width:40px;height:20px;font-size:12px}.actions wc-button:first-child{--wc-button-border-color: none}`];#t=!1;#i(s){let{left:t,top:e}=h(this),n="left",o={display:"block"},r=document.documentElement.scrollTop;if(!this.#t){if(t<260||t>260&&window.innerWidth-t>260)n="right",o.left=t+"px";else{let i=window.innerWidth-t-this.clientWidth-12;i<0&&(i=0),o.right=i+"px"}if(e<160)e+=8+this.clientHeight,n+="-bottom",o.top=e+"px";else{let i=window.innerHeight-e+8;n+="-top",o.bottom=i+"px",r>0&&(o.transform=`translateY(-${r}px)`)}this.#t=!0,this.$refs.tips.setAttribute("placement",n),this.$refs.tips.style.cssText=f(o),this.$refs.tips.$animate()}}#o(){this.#t&&(this.#t=!1,this.$refs.tips.$animate(!0))}#e(){this.$emit("confirm"),this.#o()}mounted(){this._outFn=m(this,s=>this.#o()),this._scrollFn=a(document,"scroll",s=>{if(this.#t){let t=document.documentElement.scrollTop;this.$refs.tips.style.transform=`translateY(-${t}px)`}})}unmounted(){d(document,"scroll",this._scrollFn),x(this._outFn)}render(){return c`
<main class="container"> <main class="container">
<div class="wrapper" @click=${this.#i}> <div class="wrapper" @click=${this.#i}>
<slot></slot> <slot></slot>
@ -17,7 +17,6 @@ import{css as p,html as c,Component as l,bind as a,unbind as d,styleMap as f,off
>确定</wc-button >确定</wc-button
> >
</footer> </footer>
<i class="trigon"></i>
</div> </div>
</main> </main>
`}}u.reg("popconfirm"); `}}u.reg("popconfirm");

View File

@ -1,8 +1,8 @@
import{css as a,html as n,Component as d,bind as r,styleMap as c,offset as f}from"wkit";class h extends d{static props={title:"str!"};static styles=[a`:host{display:inline-flex}.container{position:relative}.tooltip{display:none;position:fixed;z-index:9;justify-content:center;align-items:center;max-width:360px;min-width:32px;padding:6px 8px;border-radius:3px;font-size:var(--wc-tooltip-font, 14px);background:var(--wc-tooltip-background, #fff);color:var(--wc-tooltip-color, var(--color-dark-1));box-shadow:0 0 3px var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.3));word-break:break-all;-webkit-user-select:none;user-select:none}.tooltip::after{position:absolute;display:block;width:8px;height:8px;border-radius:2px;background:var(--wc-tooltip-background, #fff);content:"";transform:rotate(45deg);box-shadow:-1px -1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-top]::after{right:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-bottom]::after{right:16px;top:-4px}.tooltip[placement=right-top]::after{left:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=right-bottom]::after{left:16px;top:-4px}`];mounted(){r(this.$refs.wrap,"mouseenter",p=>{let{left:t,top:e}=f(this),i="left",o={display:"block"},l=document.documentElement.scrollTop;if(this.title.trim()!==""){if(t<360||t>360&&window.innerWidth-t>360)i="right",o.left=t+"px";else{let s=window.innerWidth-t-this.clientWidth;o.right=s+"px"}if(e<96)e+=8+this.clientHeight,i+="-bottom",o.top=e+"px";else{let s=window.innerHeight-e+8+l;i+="-top",o.bottom=s+"px"}this.$refs.tips.setAttribute("placement",i),this.$refs.tips.style.cssText=c(o),this.$refs.tips.$animate()}}),r(this.$refs.wrap,"mouseleave",p=>{this.$refs.tips.$animate(!0)})}render(){return n` import{css as a,html as n,Component as d,bind as s,styleMap as c,offset as f}from"wkit";class h extends d{static props={title:"str!"};static styles=[a`:host{display:inline-flex}.container{position:relative}.tooltip{display:none;position:fixed;z-index:9;justify-content:center;align-items:center;max-width:360px;min-width:32px;padding:6px 8px;border-radius:3px;font-size:var(--wc-tooltip-font, 14px);background:var(--wc-tooltip-background, #fff);color:var(--wc-tooltip-color, var(--color-dark-1));box-shadow:0 0 3px var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.3));word-break:break-all;-webkit-user-select:none;user-select:none}.tooltip::after{position:absolute;display:block;width:8px;height:8px;border-radius:2px;background:var(--wc-tooltip-background, #fff);content:"";transform:rotate(45deg);box-shadow:-1px -1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-top]::after{right:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=left-bottom]::after{right:16px;top:-4px}.tooltip[placement=right-top]::after{left:16px;bottom:-4px;box-shadow:1px 1px 0 var(--wc-tooltip-shadow, rgba(0, 0, 0, 0.1))}.tooltip[placement=right-bottom]::after{left:16px;top:-4px}`];mounted(){s(this.$refs.wrap,"mouseenter",p=>{let{left:t,top:e}=f(this),i="left",o={display:"block"},l=document.documentElement.scrollTop;if(this.title.trim()!==""){if(t<360||t>360&&window.innerWidth-t>360)i="right",o.left=t+"px";else{let r=window.innerWidth-t-this.clientWidth;o.right=r+"px"}if(e<96)e+=8+this.clientHeight,i+="-bottom",o.top=e+"px";else{let r=window.innerHeight-e+8+l;i+="-top",o.bottom=r+"px"}this.$refs.tips.setAttribute("placement",i),this.$refs.tips.style.cssText=c(o),this.$refs.tips.$animate()}}),s(this.$refs.wrap,"mouseleave",p=>{this.$refs.tips.$animate(!0)})}render(){return n`
<main class="container"> <main class="container">
<div class="wrapper" ref="wrap"><slot></slot></div> <div class="wrapper" ref="wrap"><slot></slot></div>
<div class="tooltip" ref="tips" #animation=${{}}> <div class="tooltip" ref="tips" #animation=${{}}>
<slot name="title">${this.title}</slot><i class="trigon"></i> <slot name="title">${this.title}</slot>
</div> </div>
</main> </main>
`}}h.reg("tooltip"); `}}h.reg("tooltip");

View File

@ -15,3 +15,11 @@ export function getContainers(all = true) {
export function getImages() { export function getImages() {
return native.handler('docker', { action: 'images' }) return native.handler('docker', { action: 'images' })
} }
export function removeContainer(id) {
return native.handler('docker', { action: 'rm', id })
}
export function removeImage(id) {
return native.handler('docker', { action: 'rmi', id })
}

View File

@ -5,7 +5,7 @@
*/ */
import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit' import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit'
import { noop, getContainers } from '../utils/index.js' import { getContainers, removeContainer } from '../utils/index.js'
class Home extends Component { class Home extends Component {
static props = { static props = {
@ -48,15 +48,18 @@ class Home extends Component {
border-bottom: 1px solid var(--color-plain-3); border-bottom: 1px solid var(--color-plain-3);
} }
.main .list { .main .list {
overflow: hidden; overflow: auto;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; gap: 3px;
width: 100%; width: 100%;
height: 100%;
padding: 12px 0; padding: 12px 0;
} }
.item { .item,
.thead {
flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
@ -67,38 +70,62 @@ class Home extends Component {
border-radius: 24px; border-radius: 24px;
--wc-icon-size: 16px; --wc-icon-size: 16px;
}
.field {
overflow: hidden;
flex: 1;
&:hover { &.flex {
background: var(--color-plain-1); display: flex;
} }
.field { &.flex-2 {
flex: 2;
}
&.name {
display: flex;
flex-direction: column;
}
&.center {
justify-content: center;
text-align: center;
}
&.gap-12 {
gap: 12px;
}
.text-ell {
overflow: hidden; overflow: hidden;
flex: 1; display: block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-blue-1);
}
&.flex-2 { .action {
flex: 2; cursor: pointer;
&.red {
color: var(--color-red-1);
} }
&.green {
&.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); color: var(--color-teal-1);
} }
} }
} }
.thead {
color: var(--color-dark-2);
}
.item {
&:hover {
background: var(--color-plain-1);
}
}
` `
] ]
@ -107,7 +134,6 @@ class Home extends Component {
mounted() { mounted() {
this.#all = +localStorage.getItem('container_mode') === 1 this.#all = +localStorage.getItem('container_mode') === 1
console.log('<><><>', this.#all)
this.#fetch() this.#fetch()
} }
@ -115,8 +141,17 @@ class Home extends Component {
let list = await getContainers(this.#all) let list = await getContainers(this.#all)
list.forEach(it => { list.forEach(it => {
it.port = it.port.replace('0.0.0.0:', '').replace(':::', '') it.image = it.image.split(':')
it.cmd = it.cmd.replace(/^"|"$/, '')
it.port = it.port
.replace('0.0.0.0:', '')
.replace(':::', '')
.replace(/\/tcp/g, '')
it.state =
it.state === 'running' ? '🟢' : it.state === 'created' ? '⚪' : '🔴'
}) })
console.log(list)
this.containers = list this.containers = list
} }
@ -136,6 +171,12 @@ class Home extends Component {
this.$requestUpdate() this.$requestUpdate()
} }
async #remove(item) {
console.log(item)
await removeContainer(item.id)
this.#fetch()
}
render() { render() {
let list = this.containers let list = this.containers
let txt = this.#input let txt = this.#input
@ -158,6 +199,14 @@ class Home extends Component {
</wc-space> </wc-space>
<ul class="list"> <ul class="list">
<li class="thead">
<span class="field flex-2">容器</span>
<span class="field flex-2">所属镜像</span>
<span class="field">命令</span>
<span class="field">端口</span>
<span class="field center">状态</span>
<span class="field center">操作</span>
</li>
${list.map( ${list.map(
it => html` it => html`
<li class="item"> <li class="item">
@ -168,18 +217,30 @@ class Home extends Component {
<code>${it.id}</code> <code>${it.id}</code>
</section> </section>
<section class="field flex-2"> <section class="field flex-2">
<wc-tooltip title=${it.image}> <span class="text-ell">${it.image[0]}</span>
<span class="text-ell">${it.image}</span> <code>${it.image[1]}</code>
</section>
<section class="field">
<wc-tooltip title=${it.cmd}>
<span class="text-ell">${it.cmd}</span>
</wc-tooltip> </wc-tooltip>
</section> </section>
<section class="field center">${it.state}</section>
<section class="field"> <section class="field">
<wc-tooltip title=${it.port}> <wc-tooltip title=${it.port}>
<span class="text-ell">${it.port}</span> <span class="text-ell">${it.port}</span>
</wc-tooltip> </wc-tooltip>
</section> </section>
<section class="field center"> <section class="field center">${it.state}</section>
<wc-icon name="trash"></wc-icon> <section class="field flex gap-12 center">
<span class="action ${it.state === '🟢' ? 'red' : 'green'}"
>${it.state === '🟢' ? '⏹' : '⏸'}</span
>
<wc-popconfirm
title="是否要删除此容器?"
@confirm=${ev => this.#remove(it)}
>
<span class="action">🗑</span>
</wc-popconfirm>
</section> </section>
</li> </li>
` `

View File

@ -48,15 +48,18 @@ class Images extends Component {
border-bottom: 1px solid var(--color-plain-3); border-bottom: 1px solid var(--color-plain-3);
} }
.main .list { .main .list {
overflow: hidden; overflow: auto;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; gap: 3px;
width: 100%; width: 100%;
height: 100%;
padding: 12px 0; padding: 12px 0;
} }
.item { .item,
.thead {
flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
@ -67,36 +70,40 @@ class Images extends Component {
border-radius: 24px; border-radius: 24px;
--wc-icon-size: 16px; --wc-icon-size: 16px;
}
.field {
overflow: hidden;
flex: 1;
&:hover { &.flex-2 {
background: var(--color-plain-1); flex: 2;
} }
.field { &.name {
display: flex;
flex-direction: column;
}
&.center {
text-align: center;
}
.text-ell {
overflow: hidden; overflow: hidden;
flex: 1; display: block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-teal-1);
}
}
&.flex-2 { .thead {
flex: 2; color: var(--color-dark-2);
} }
.item {
&.name { &:hover {
display: flex; background: var(--color-plain-1);
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);
}
} }
} }
` `
@ -138,6 +145,13 @@ class Images extends Component {
</wc-space> </wc-space>
<ul class="list"> <ul class="list">
<li class="thead">
<span class="field flex-2">镜像</span>
<span class="field">版本</span>
<span class="field center">大小</span>
<span class="field center">创建时间</span>
<span class="field center">操作</span>
</li>
${list.map( ${list.map(
it => html` it => html`
<li class="item"> <li class="item">