更新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'],
'size': it['Size'],
'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
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)
self.set_default_size(width, height)
self.resize(width, height)

View File

@ -60,6 +60,12 @@ class Application(Gtk.Application):
case '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;
}
.item.active {
color: var(--color-orange-3);
color: var(--color-blue-3);
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">
<div class="wrapper" @click=${this.#i}>
<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
>
</footer>
<i class="trigon"></i>
</div>
</main>
`}}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">
<div class="wrapper" ref="wrap"><slot></slot></div>
<div class="tooltip" ref="tips" #animation=${{}}>
<slot name="title">${this.title}</slot><i class="trigon"></i>
<slot name="title">${this.title}</slot>
</div>
</main>
`}}h.reg("tooltip");

View File

@ -15,3 +15,11 @@ export function getContainers(all = true) {
export function getImages() {
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 { noop, getContainers } from '../utils/index.js'
import { getContainers, removeContainer } from '../utils/index.js'
class Home extends Component {
static props = {
@ -48,15 +48,18 @@ class Home extends Component {
border-bottom: 1px solid var(--color-plain-3);
}
.main .list {
overflow: hidden;
overflow: auto;
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
width: 100%;
height: 100%;
padding: 12px 0;
}
.item {
.item,
.thead {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 12px;
@ -67,38 +70,62 @@ class Home extends Component {
border-radius: 24px;
--wc-icon-size: 16px;
}
.field {
overflow: hidden;
flex: 1;
&:hover {
background: var(--color-plain-1);
&.flex {
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;
flex: 1;
display: block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-blue-1);
}
&.flex-2 {
flex: 2;
.action {
cursor: pointer;
&.red {
color: var(--color-red-1);
}
&.name {
display: flex;
flex-direction: column;
}
&.center {
text-align: center;
}
.text-ell {
overflow: hidden;
display: block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
&.green {
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() {
this.#all = +localStorage.getItem('container_mode') === 1
console.log('<><><>', this.#all)
this.#fetch()
}
@ -115,8 +141,17 @@ class Home extends Component {
let list = await getContainers(this.#all)
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
}
@ -136,6 +171,12 @@ class Home extends Component {
this.$requestUpdate()
}
async #remove(item) {
console.log(item)
await removeContainer(item.id)
this.#fetch()
}
render() {
let list = this.containers
let txt = this.#input
@ -158,6 +199,14 @@ class Home extends Component {
</wc-space>
<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(
it => html`
<li class="item">
@ -168,18 +217,30 @@ class Home extends Component {
<code>${it.id}</code>
</section>
<section class="field flex-2">
<wc-tooltip title=${it.image}>
<span class="text-ell">${it.image}</span>
<span class="text-ell">${it.image[0]}</span>
<code>${it.image[1]}</code>
</section>
<section class="field">
<wc-tooltip title=${it.cmd}>
<span class="text-ell">${it.cmd}</span>
</wc-tooltip>
</section>
<section class="field center">${it.state}</section>
<section class="field">
<wc-tooltip title=${it.port}>
<span class="text-ell">${it.port}</span>
</wc-tooltip>
</section>
<section class="field center">
<wc-icon name="trash"></wc-icon>
<section class="field center">${it.state}</section>
<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>
</li>
`

View File

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