Skip to content

Commit 9d6d08d

Browse files
committed
fix(ui): improve mobile sites layout
1 parent bbeaf14 commit 9d6d08d

4 files changed

Lines changed: 236 additions & 3 deletions

File tree

src/frontend/src/assets/tokens.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ html:not(.dark) .el-button {
462462
.el-button.is-disabled,
463463
.el-button.is-disabled:hover,
464464
.el-button.is-disabled:active {
465-
opacity: 0.45 !important;
465+
opacity: 0.72 !important;
466466
cursor: not-allowed !important;
467467
/* Preserve the variant's own colours — DO NOT override bg/border/color here
468468
or the disabled state becomes a uniform grey that hides the button's
@@ -501,6 +501,17 @@ html:not(.dark) .el-button {
501501
font-size: 0.82rem;
502502
}
503503

504+
.el-input--small .el-input__wrapper,
505+
.el-select--small .el-select__wrapper,
506+
.el-input-number--small .el-input__wrapper {
507+
min-height: 32px !important;
508+
}
509+
510+
.el-radio-button__inner {
511+
min-height: 32px !important;
512+
line-height: 30px !important;
513+
}
514+
504515
/* Tags */
505516
.el-tag {
506517
border-radius: 4px;
@@ -510,7 +521,8 @@ html:not(.dark) .el-button {
510521
}
511522

512523
.el-tag--small {
513-
height: 22px;
524+
min-height: 24px;
525+
height: 24px;
514526
padding: 0 6px;
515527
}
516528

src/frontend/src/components/layout/AppStatusBar.vue

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,34 @@ function formatMem(bytes: number): string {
142142
margin-left: auto;
143143
color: var(--wdc-text-3);
144144
}
145+
146+
@media (max-width: 520px) {
147+
.status-bar {
148+
gap: 8px;
149+
padding: 0 14px;
150+
}
151+
152+
.status-bar:not(.status-bar-simple) {
153+
gap: 7px;
154+
overflow: hidden;
155+
}
156+
157+
.status-bar:not(.status-bar-simple) .status-sep,
158+
.status-bar:not(.status-bar-simple) .status-right {
159+
display: none;
160+
}
161+
162+
.status-bar-simple {
163+
justify-content: center;
164+
}
165+
166+
.status-bar-simple .status-item-center {
167+
position: static;
168+
transform: none;
169+
}
170+
171+
.status-bar-simple .status-right {
172+
display: none;
173+
}
174+
}
145175
</style>

src/frontend/src/components/pages/Sites.vue

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,73 @@
3232
</div>
3333

3434
<div class="page-body">
35+
<div class="sites-mobile-list">
36+
<article
37+
v-for="site in filteredSites"
38+
:key="site.domain"
39+
class="site-mobile-card"
40+
>
41+
<div class="site-mobile-main" @click="editSite(site)">
42+
<div class="site-mobile-title-row">
43+
<span class="site-mobile-title">{{ site.domain }}</span>
44+
<el-tag
45+
v-if="site.sslEnabled"
46+
size="small"
47+
type="success"
48+
effect="dark"
49+
class="cell-tag"
50+
>SSL</el-tag>
51+
</div>
52+
<div class="site-mobile-meta mono" :title="site.documentRoot">
53+
{{ site.documentRoot }}
54+
</div>
55+
<div class="site-mobile-badges">
56+
<el-tag
57+
v-if="rowRuntimeLabel(site)"
58+
size="small"
59+
effect="dark"
60+
class="runtime-tag runtime-php"
61+
>{{ rowRuntimeLabel(site) }}</el-tag>
62+
<span v-if="site.aliases?.length" class="alias-dot">+{{ site.aliases.length }}</span>
63+
<span v-if="site.aliases?.length" class="site-mobile-alias mono">
64+
{{ site.aliases[0] }}
65+
</span>
66+
</div>
67+
</div>
68+
<div class="site-mobile-actions">
69+
<el-switch
70+
:model-value="site.enabled !== false"
71+
:loading="togglingEnabled === site.domain"
72+
size="small"
73+
inline-prompt
74+
:title="site.enabled !== false ? 'Povoleno' : 'Zakázáno'"
75+
@change="(v: boolean | string | number) => toggleSiteEnabled(site, v)"
76+
@click.stop
77+
/>
78+
<el-button size="small" type="primary" @click.stop="editSite(site)">{{ $t('common.edit') }}</el-button>
79+
<el-dropdown
80+
trigger="click"
81+
:teleported="true"
82+
:popper-options="{ modifiers: [{ name: 'preventOverflow', options: { boundary: 'viewport', padding: 8 } }] }"
83+
@command="(cmd: string) => handleRowAction(cmd, site)"
84+
>
85+
<el-button size="small" circle :aria-label="$t('sites.card.moreActions', { domain: site.domain })"><el-icon><MoreFilled /></el-icon></el-button>
86+
<template #dropdown>
87+
<el-dropdown-menu>
88+
<el-dropdown-item command="open">
89+
{{ $t('sites.open') }}
90+
</el-dropdown-item>
91+
<el-dropdown-item command="detect">
92+
<el-icon><RefreshRight /></el-icon> {{ $t('sites.detect') }}
93+
</el-dropdown-item>
94+
<el-dropdown-item command="delete" divided class="danger-item">{{ $t('sites.delete') }}</el-dropdown-item>
95+
</el-dropdown-menu>
96+
</template>
97+
</el-dropdown>
98+
</div>
99+
</article>
100+
</div>
101+
35102
<el-table
36103
:data="filteredSites"
37104
v-loading="sitesStore.loading"
@@ -304,7 +371,7 @@ import { usePluginsStore } from '../../stores/plugins'
304371
import type { SiteInfo } from '../../api/types'
305372
import { fetchDockerComposeStatus, fetchPhpVersions, fetchSystem, daemonBaseUrl, daemonAuthHeaders as authHeaders, type DockerComposeStatus } from '../../api/daemon'
306373
import { errorMessage } from '../../utils/errors'
307-
import { MoreFilled } from '@element-plus/icons-vue'
374+
import { MoreFilled, RefreshRight } from '@element-plus/icons-vue'
308375
import SitesListSimple from './SitesListSimple.vue'
309376
import PluginSlot from '../shared/PluginSlot.vue'
310377
import siteTemplatesConfig from '../../config/site-templates.json'
@@ -384,6 +451,12 @@ function phpFullLabel(stored: string): string {
384451
const match = phpVersions.value.find(p => p.value === stored)
385452
return match ? match.label : `PHP ${stored}`
386453
}
454+
455+
function rowRuntimeLabel(site: SiteInfo): string {
456+
if (site.nodeUpstreamPort && site.nodeUpstreamPort > 0) return `Node:${site.nodeUpstreamPort}`
457+
if (site.phpVersion && site.phpVersion !== 'none') return phpFullLabel(site.phpVersion)
458+
return 'Static'
459+
}
387460
// Native-looking Document Root placeholder. Windows keeps the legacy
388461
// C:\work\htdocs\<app> hint; macOS/Linux get a path rooted at the user's
389462
// home dir so the placeholder itself isn't misleading on those OSes.
@@ -778,6 +851,10 @@ function handleRowAction(cmd: string, row: SiteInfo) {
778851
padding: 0 24px 24px;
779852
}
780853
854+
.sites-mobile-list {
855+
display: none;
856+
}
857+
781858
.sites-table :deep(.el-table__header) {
782859
background: var(--wdc-surface-2);
783860
}
@@ -953,6 +1030,117 @@ function handleRowAction(cmd: string, row: SiteInfo) {
9531030
}
9541031
}
9551032
1033+
@media (max-width: 700px) {
1034+
.page-header {
1035+
align-items: flex-start;
1036+
flex-direction: column;
1037+
gap: 12px;
1038+
padding: 18px 16px 0;
1039+
margin-bottom: 14px;
1040+
}
1041+
1042+
.header-actions {
1043+
display: grid;
1044+
grid-template-columns: 1fr 1fr;
1045+
gap: 8px;
1046+
width: 100%;
1047+
}
1048+
1049+
.header-actions :deep(.el-button) {
1050+
width: 100%;
1051+
margin-left: 0 !important;
1052+
}
1053+
1054+
.header-actions :deep(.el-button--primary) {
1055+
grid-column: 1 / -1;
1056+
}
1057+
1058+
.search-bar,
1059+
.page-body {
1060+
padding-left: 16px;
1061+
padding-right: 16px;
1062+
}
1063+
1064+
.search-bar :deep(.el-input) {
1065+
max-width: none !important;
1066+
width: 100%;
1067+
}
1068+
1069+
.sites-table {
1070+
display: none;
1071+
}
1072+
1073+
.sites-mobile-list {
1074+
display: grid;
1075+
gap: 10px;
1076+
}
1077+
1078+
.site-mobile-card {
1079+
display: grid;
1080+
grid-template-columns: minmax(0, 1fr) auto;
1081+
gap: 12px;
1082+
align-items: center;
1083+
min-width: 0;
1084+
padding: 12px;
1085+
background: var(--wdc-surface);
1086+
border: 1px solid var(--wdc-border);
1087+
border-radius: 8px;
1088+
}
1089+
1090+
.site-mobile-main {
1091+
min-width: 0;
1092+
cursor: pointer;
1093+
}
1094+
1095+
.site-mobile-title-row,
1096+
.site-mobile-badges,
1097+
.site-mobile-actions {
1098+
display: flex;
1099+
align-items: center;
1100+
gap: 6px;
1101+
min-width: 0;
1102+
}
1103+
1104+
.site-mobile-title {
1105+
min-width: 0;
1106+
overflow: hidden;
1107+
text-overflow: ellipsis;
1108+
white-space: nowrap;
1109+
color: var(--wdc-text);
1110+
font-size: 0.96rem;
1111+
font-weight: 700;
1112+
}
1113+
1114+
.site-mobile-meta {
1115+
margin-top: 4px;
1116+
color: var(--wdc-text-3);
1117+
font-size: 0.72rem;
1118+
overflow: hidden;
1119+
text-overflow: ellipsis;
1120+
white-space: nowrap;
1121+
}
1122+
1123+
.site-mobile-badges {
1124+
margin-top: 8px;
1125+
flex-wrap: wrap;
1126+
}
1127+
1128+
.site-mobile-alias {
1129+
min-width: 0;
1130+
max-width: 150px;
1131+
overflow: hidden;
1132+
text-overflow: ellipsis;
1133+
white-space: nowrap;
1134+
color: var(--wdc-text-2);
1135+
font-size: 0.72rem;
1136+
}
1137+
1138+
.site-mobile-actions {
1139+
justify-content: flex-end;
1140+
flex-wrap: nowrap;
1141+
}
1142+
}
1143+
9561144
/* Row actions: keep switch + edit + overflow on one line, prevent
9571145
wrapping into 2 rows which collides with the fixed-right column
9581146
border. */

src/frontend/src/plugin-components/CloudflareTunnelBadge.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const cloudflaredRunning = computed(() =>
4242
display: inline-flex;
4343
align-items: center;
4444
gap: 4px;
45+
box-sizing: border-box;
46+
min-height: 24px !important;
47+
line-height: 18px;
4548
padding: 2px 8px;
4649
background: rgba(243, 128, 32, 0.12);
4750
color: #f38020;

0 commit comments

Comments
 (0)