Skip to content

Commit 7230eb5

Browse files
committed
设置:服务器支持编辑(只改地址、端口时沿用登录,不用输密码),保留重新登录;开发桥日志不打印密码
1 parent 8f7bd09 commit 7230eb5

4 files changed

Lines changed: 94 additions & 24 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Windows 桌面 Emby 客户端:桌面软件风格的浏览界面,播放交给 mpv。
44

5-
- 多服务器:地址、端口分开填,随时切换;支持反向代理挂在子路径下的服务器
5+
- 多服务器:地址、端口分开填,随时切换;可编辑(换地址、端口不用重新登录)或重新登录;支持反向代理挂在子路径下的服务器
66
- 浏览:首页轮播、继续观看、各媒体库最新;媒体库分页、搜索、收藏;剧集详情(季、集、演职人员、类似作品、媒体信息)
77
- 播放:mpv + ModernZ 界面(顶栏居中标题 + 网速);续播、整季连播、按进度预加载下一集、选集菜单(F8)、换集沿用音轨与字幕、外挂字幕
88
- 进度用 Emby 标准的 Sessions 接口回传,其他客户端同步可见;token 不出现在 mpv 的播放列表与统计页里

‎src-tauri/src/main.rs‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,43 @@ async fn server_add(
171171
Ok(view)
172172
}
173173

174+
/// 编辑服务器(id 不变,是当前服务器的话仍是当前):改地址、端口、用户名,或者重新登录。
175+
/// 先用新地址连一次服务器(顺带更新服务器名),连不上就不保存。
176+
/// 只改地址时(换域名、换端口、换反代)沿用原来的 token,不用输密码;
177+
/// 改了用户名、填了密码或者原来就没登录时,用这里的用户名和密码重新登录。
178+
#[tauri::command]
179+
async fn server_edit(
180+
app: State<'_, App>,
181+
id: String,
182+
url: String,
183+
port: Option<u16>,
184+
user: String,
185+
password: String,
186+
) -> Result<ServerView, Error> {
187+
let (url, port) = config::normalize(&url, port).map_err(Error::Other)?;
188+
let (mut s, device_id) = {
189+
let c = app.cfg();
190+
let s = c.servers.iter().find(|s| s.id == id).cloned();
191+
(s.ok_or_else(|| Error::Other("服务器不存在".into()))?, c.device_id.clone())
192+
};
193+
let relogin = !password.is_empty() || user != s.user || s.token.is_empty();
194+
(s.url, s.port, s.user) = (url, port, user);
195+
let base = s.api_base();
196+
s.name = emby::server_name(&app.http, &base).await?;
197+
if relogin {
198+
(s.user_id, s.token) = emby::login(&app.http, &base, &device_id, &s.user, &password).await?;
199+
}
200+
let view = s.view();
201+
app.update(|c| {
202+
let t = c.servers.iter_mut().find(|t| t.id == id)?;
203+
*t = s;
204+
Some(())
205+
})?
206+
.ok_or_else(|| Error::Other("服务器不存在".into()))?;
207+
Ok(view)
208+
}
209+
210+
/// 重新登录(只要密码):token 失效或者想换一个新 token 时用。
174211
#[tauri::command]
175212
async fn server_login(
176213
app: State<'_, App>,
@@ -357,6 +394,7 @@ fn main() {
357394
.invoke_handler(tauri::generate_handler![
358395
config_get,
359396
server_add,
397+
server_edit,
360398
server_login,
361399
server_remove,
362400
server_use,

‎ui/dev/bridge.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ export function devBridge(): Plugin {
7474
if (!r.ok) return reply(500, { kind: r.status === 401 ? 'unauthorized' : 'status', message: `服务器返回 ${r.status}:${text.slice(0, 200)}` })
7575
return reply(200, text ? JSON.parse(text) : null)
7676
}
77-
// play / server_* / settings_set:浏览器里没有 mpv,也不改本地配置,只记一笔
78-
console.log(`[dev-bridge] ${cmd}`, JSON.stringify(args))
77+
// play / server_* / settings_set:浏览器里没有 mpv,也不改本地配置,只记一笔(密码不打印)
78+
console.log(`[dev-bridge] ${cmd}`, JSON.stringify(args, (k, v) => (k === 'password' ? '***' : v)))
7979
return reply(200, null)
8080
} catch (e) {
8181
return reply(500, { kind: 'other', message: String(e) })

‎ui/src/routes/Settings.svelte‎

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
<!-- 设置:服务器(添加 / 切换 / 重新登录 / 删除,地址与端口分开填)+ 播放(mpv 路径、预加载进度、个人 mpv 配置的位置)+ 关于(界面用哪块 GPU)。分组列表样式。 -->
1+
<!-- 设置:服务器(添加 / 切换 / 编辑 / 重新登录 / 删除,地址与端口分开填)+ 播放(mpv 路径、预加载进度、个人 mpv 配置的位置)+ 关于(界面用哪块 GPU)。分组列表样式。 -->
22
<script lang="ts">
33
import { invoke } from '@tauri-apps/api/core'
4-
import { Check, LogIn, Plus, Trash2 } from '@lucide/svelte'
4+
import { Check, LogIn, Pencil, Plus, Trash2 } from '@lucide/svelte'
55
import { errText, loadConfig, refresh, session } from '../lib/api.svelte'
66
import { go } from '../lib/router.svelte'
77
8-
let form = $state({ url: '', port: '', user: '', password: '' })
8+
const blank = { url: '', port: '', user: '', password: '' }
9+
let form = $state({ ...blank })
10+
/** 正在编辑的服务器 id;和「添加」共用 form。 */
11+
let editing = $state<string | null>(null)
912
let relogin = $state<{ id: string; password: string } | null>(null)
1013
let busy = $state(false)
1114
let msg = $state<{ text: string; ok: boolean } | null>(null)
@@ -28,14 +31,35 @@
2831
}
2932
}
3033
34+
function formPort() {
35+
const port = form.port.trim() ? Number(form.port) : null
36+
if (port !== null && !(port > 0 && port < 65536)) throw { message: '端口应为 1–65535' }
37+
return port
38+
}
3139
const add = () =>
3240
run(async () => {
33-
const port = form.port.trim() ? Number(form.port) : null
34-
if (port !== null && !(port > 0 && port < 65536)) throw { message: '端口应为 1–65535' }
35-
await invoke('server_add', { url: form.url, port, user: form.user, password: form.password })
36-
form = { url: '', port: '', user: '', password: '' }
41+
await invoke('server_add', { url: form.url, port: formPort(), user: form.user, password: form.password })
42+
form = { ...blank }
3743
go('/')
3844
})
45+
function startEdit(s: { id: string; url: string; port: number; user: string }) {
46+
form = { url: s.url, port: String(s.port), user: s.user, password: '' }
47+
editing = s.id
48+
adding = false
49+
relogin = null
50+
}
51+
function startAdd() {
52+
form = { ...blank }
53+
editing = null
54+
adding = true
55+
}
56+
/** 只改地址时后端沿用原 token,不用填密码;改了用户名或填了密码就重新登录。 */
57+
const save = (id: string) =>
58+
run(async () => {
59+
await invoke('server_edit', { id, url: form.url, port: formPort(), user: form.user, password: form.password })
60+
editing = null
61+
form = { ...blank }
62+
}, '已保存')
3963
function login() {
4064
const r = relogin
4165
if (!r) return
@@ -72,7 +96,22 @@
7296

7397
<h3>服务器</h3>
7498
<div class="group">
99+
{#snippet serverForm(submit: () => void, label: string, pwHint: string, cancel: (() => void) | null)}
100+
<form class="row form" onsubmit={(e) => { e.preventDefault(); submit() }}>
101+
<input class="url" placeholder="地址,如 https://emby.example.com" bind:value={form.url} required spellcheck="false" />
102+
<input class="port" placeholder="端口(默认按协议)" bind:value={form.port} inputmode="numeric" />
103+
<input placeholder="用户名" bind:value={form.user} required autocomplete="username" spellcheck="false" />
104+
<input type="password" placeholder={pwHint} bind:value={form.password} autocomplete="current-password" />
105+
<div class="btns">
106+
<button class="btn primary" disabled={busy}>{label}</button>
107+
{#if cancel}<button type="button" class="btn ghost" onclick={cancel}>取消</button>{/if}
108+
</div>
109+
</form>
110+
{/snippet}
75111
{#each session.cfg?.servers ?? [] as s (s.id)}
112+
{#if editing === s.id}
113+
{@render serverForm(() => save(s.id), '保存', '密码(只改地址可不填)', () => (editing = null))}
114+
{:else}
76115
<div class="row">
77116
<div class="grow">
78117
<div class="name">{s.name}{#if s.id === session.cfg?.active}<span class="tag">当前</span>{/if}</div>
@@ -85,24 +124,17 @@
85124
<button class="btn ghost" onclick={() => (relogin = null)}>取消</button>
86125
{:else}
87126
{#if s.id !== session.cfg?.active}<button class="btn" disabled={busy} onclick={() => use(s.id)}><Check size={15} /> 使用</button>{/if}
88-
<button class="btn" disabled={busy} onclick={() => (relogin = { id: s.id, password: '' })}><LogIn size={15} /> 重新登录</button>
127+
<button class="btn" disabled={busy} onclick={() => startEdit(s)}><Pencil size={15} /> 编辑</button>
128+
<button class="btn" disabled={busy} onclick={() => ((relogin = { id: s.id, password: '' }), (editing = null))}><LogIn size={15} /> 重新登录</button>
89129
<button class="btn icon danger" title="删除" disabled={busy} onclick={() => remove(s.id, s.name)}><Trash2 size={15} /></button>
90130
{/if}
91131
</div>
132+
{/if}
92133
{/each}
93134
{#if adding}
94-
<form class="row add" onsubmit={(e) => { e.preventDefault(); add() }}>
95-
<input class="url" placeholder="地址,如 https://emby.example.com" bind:value={form.url} required spellcheck="false" />
96-
<input class="port" placeholder="端口(默认按协议)" bind:value={form.port} inputmode="numeric" />
97-
<input placeholder="用户名" bind:value={form.user} required autocomplete="username" spellcheck="false" />
98-
<input type="password" placeholder="密码" bind:value={form.password} autocomplete="current-password" />
99-
<div class="btns">
100-
<button class="btn primary" disabled={busy}><Plus size={15} /> 添加并登录</button>
101-
{#if session.cfg?.servers.length}<button type="button" class="btn ghost" onclick={() => (adding = false)}>取消</button>{/if}
102-
</div>
103-
</form>
135+
{@render serverForm(add, '添加并登录', '密码', session.cfg?.servers.length ? () => (adding = false) : null)}
104136
{:else}
105-
<button class="row link" onclick={() => (adding = true)}><Plus size={16} /> 添加服务器</button>
137+
<button class="row link" onclick={startAdd}><Plus size={16} /> 添加服务器</button>
106138
{/if}
107139
</div>
108140

@@ -155,8 +187,8 @@
155187
}
156188
input:not([type='range']):focus { background: var(--bg); box-shadow: inset 0 -2px 0 var(--accent); }
157189
input[type='range'] { accent-color: var(--accent); }
158-
.add { display: grid; grid-template-columns: 170px 1fr 1fr; gap: 10px; }
159-
.add .url, .add .btns { grid-column: 1 / -1; }
190+
.form { display: grid; grid-template-columns: 170px 1fr 1fr; gap: 10px; }
191+
.form .url, .form .btns { grid-column: 1 / -1; }
160192
.btns { display: flex; gap: 8px; }
161193
code { font-family: ui-monospace, Consolas, monospace; font-size: 11px; padding: 0 4px; border-radius: 4px; background: var(--fill); }
162194
.note { margin: 10px var(--gutter); font-size: 12px; color: var(--text-3); line-height: 1.8; }

0 commit comments

Comments
 (0)