video settings: per-connection Test button (mirrors music's testConnection)
Each video connection item (TMDB/TVDB) now has a Test button that behaves like
music's: saves the key, hits POST /api/video/enrichment/<svc>/test, and toasts
the result via the shared showToast — isolated (own endpoint, own data-attr
handler, reuses the .test-button CSS).
- Client .test() pings TMDB /configuration and TVDB /login to verify the key.
- Endpoint returns {success,message,error}; unknown service -> 404.
94 tests green; music untouched.
This commit is contained in:
parent
d35bb695ae
commit
2901e4ec4d
6 changed files with 75 additions and 1 deletions
|
|
@ -86,6 +86,18 @@ def register_routes(bp):
|
||||||
w.resume()
|
w.resume()
|
||||||
return jsonify({"status": "running"})
|
return jsonify({"status": "running"})
|
||||||
|
|
||||||
|
@bp.route("/enrichment/<service>/test", methods=["POST"])
|
||||||
|
def video_enrichment_test(service):
|
||||||
|
w = engine().worker(service)
|
||||||
|
if not w:
|
||||||
|
return jsonify({"success": False, "error": "unknown service"}), 404
|
||||||
|
try:
|
||||||
|
ok, msg = w.client.test()
|
||||||
|
return jsonify({"success": bool(ok), "message": msg, "error": None if ok else msg})
|
||||||
|
except Exception:
|
||||||
|
logger.exception("video enrichment test failed for %s", service)
|
||||||
|
return jsonify({"success": False, "error": "Test failed"})
|
||||||
|
|
||||||
@bp.route("/enrichment/<service>/breakdown", methods=["GET"])
|
@bp.route("/enrichment/<service>/breakdown", methods=["GET"])
|
||||||
def video_enrichment_breakdown(service):
|
def video_enrichment_breakdown(service):
|
||||||
from . import get_video_db
|
from . import get_video_db
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,21 @@ class TMDBClient:
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
return bool(self.api_key)
|
return bool(self.api_key)
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
if not self.api_key:
|
||||||
|
return False, "No TMDB API key set"
|
||||||
|
import requests
|
||||||
|
try:
|
||||||
|
r = requests.get(self.BASE + "/configuration", params={"api_key": self.api_key}, timeout=12)
|
||||||
|
if r.status_code == 200:
|
||||||
|
return True, "TMDB connection OK"
|
||||||
|
if r.status_code == 401:
|
||||||
|
return False, "Invalid TMDB API key"
|
||||||
|
return False, "TMDB returned HTTP " + str(r.status_code)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("TMDB test failed")
|
||||||
|
return False, "Could not reach TMDB"
|
||||||
|
|
||||||
def match(self, kind, title, year):
|
def match(self, kind, title, year):
|
||||||
if not self.api_key or not title:
|
if not self.api_key or not title:
|
||||||
return None
|
return None
|
||||||
|
|
@ -77,6 +92,18 @@ class TVDBClient:
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
return bool(self.api_key)
|
return bool(self.api_key)
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
if not self.api_key:
|
||||||
|
return False, "No TVDB API key set"
|
||||||
|
try:
|
||||||
|
token = self._auth()
|
||||||
|
if token:
|
||||||
|
return True, "TVDB connection OK"
|
||||||
|
return False, "TVDB login failed — check the key"
|
||||||
|
except Exception:
|
||||||
|
logger.exception("TVDB test failed")
|
||||||
|
return False, "Could not reach TVDB"
|
||||||
|
|
||||||
def _auth(self):
|
def _auth(self):
|
||||||
if self._token:
|
if self._token:
|
||||||
return self._token
|
return self._token
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ def test_blueprint_exposes_dashboard_route():
|
||||||
assert "/api/video/enrichment/<service>/status" in rules
|
assert "/api/video/enrichment/<service>/status" in rules
|
||||||
assert "/api/video/enrichment/<service>/unmatched" in rules
|
assert "/api/video/enrichment/<service>/unmatched" in rules
|
||||||
assert "/api/video/enrichment/config" in rules
|
assert "/api/video/enrichment/config" in rules
|
||||||
|
assert "/api/video/enrichment/<service>/test" in rules
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_endpoint_returns_zeroed_json(tmp_path):
|
def test_dashboard_endpoint_returns_zeroed_json(tmp_path):
|
||||||
|
|
@ -100,6 +101,7 @@ def test_enrichment_endpoints(tmp_path):
|
||||||
class FakeClient:
|
class FakeClient:
|
||||||
enabled = True
|
enabled = True
|
||||||
def match(self, *a): return None
|
def match(self, *a): return None
|
||||||
|
def test(self): return (True, "ok")
|
||||||
|
|
||||||
db = VideoDatabase(database_path=str(tmp_path / "video_library.db"))
|
db = VideoDatabase(database_path=str(tmp_path / "video_library.db"))
|
||||||
videoapi._video_db = db
|
videoapi._video_db = db
|
||||||
|
|
@ -125,6 +127,8 @@ def test_enrichment_endpoints(tmp_path):
|
||||||
assert client.post("/api/video/enrichment/tmdb/resume").get_json()["status"] == "running"
|
assert client.post("/api/video/enrichment/tmdb/resume").get_json()["status"] == "running"
|
||||||
assert client.post("/api/video/enrichment/tmdb/retry",
|
assert client.post("/api/video/enrichment/tmdb/retry",
|
||||||
json={"kind": "movie", "scope": "failed"}).get_json()["reset"] == 1
|
json={"kind": "movie", "scope": "failed"}).get_json()["reset"] == 1
|
||||||
|
assert client.post("/api/video/enrichment/tmdb/test").get_json()["success"] is True
|
||||||
|
assert client.post("/api/video/enrichment/nope/test").status_code == 404
|
||||||
assert client.get("/api/video/enrichment/nope/status").status_code == 404
|
assert client.get("/api/video/enrichment/nope/status").status_code == 404
|
||||||
finally:
|
finally:
|
||||||
videoapi._video_db = None
|
videoapi._video_db = None
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,8 @@ def test_video_side_hides_music_api_config_and_shows_placeholders():
|
||||||
assert 'class="api-service-frame stg-service" data-video-service="tvdb"' in _INDEX
|
assert 'class="api-service-frame stg-service" data-video-service="tvdb"' in _INDEX
|
||||||
# They must NOT carry the music-hooked data-service attribute.
|
# They must NOT carry the music-hooked data-service attribute.
|
||||||
assert 'data-service="tmdb"' not in _INDEX and 'data-service="tvdb"' not in _INDEX
|
assert 'data-service="tmdb"' not in _INDEX and 'data-service="tvdb"' not in _INDEX
|
||||||
|
# Each connection item has a Test button (like music's connections).
|
||||||
|
assert 'data-video-test-service="tmdb"' in _INDEX and 'data-video-test-service="tvdb"' in _INDEX
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_enrichment_buttons_present():
|
def test_dashboard_enrichment_buttons_present():
|
||||||
|
|
|
||||||
|
|
@ -4856,6 +4856,7 @@
|
||||||
<div class="callback-help">Get your key from <a href="https://www.themoviedb.org/settings/api" target="_blank" style="color: #01b4e4;">TMDB API Settings</a></div>
|
<div class="callback-help">Get your key from <a href="https://www.themoviedb.org/settings/api" target="_blank" style="color: #01b4e4;">TMDB API Settings</a></div>
|
||||||
<div class="callback-help">Primary movie & TV metadata source.</div>
|
<div class="callback-help">Primary movie & TV metadata source.</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="test-button" type="button" data-video-test-service="tmdb">Test TMDB</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="api-service-frame stg-service" data-video-service="tvdb">
|
<div class="api-service-frame stg-service" data-video-service="tvdb">
|
||||||
|
|
@ -4873,6 +4874,7 @@
|
||||||
<div class="callback-help">Get your key from <a href="https://thetvdb.com/dashboard/account/apikey" target="_blank" style="color: #6cd391;">TheTVDB API</a></div>
|
<div class="callback-help">Get your key from <a href="https://thetvdb.com/dashboard/account/apikey" target="_blank" style="color: #6cd391;">TheTVDB API</a></div>
|
||||||
<div class="callback-help">TV series metadata source.</div>
|
<div class="callback-help">TV series metadata source.</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="test-button" type="button" data-video-test-service="tvdb">Test TVDB</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,31 @@
|
||||||
function saveKeys() {
|
function saveKeys() {
|
||||||
var t = document.getElementById('tmdb-api-key');
|
var t = document.getElementById('tmdb-api-key');
|
||||||
var v = document.getElementById('tvdb-api-key');
|
var v = document.getElementById('tvdb-api-key');
|
||||||
fetch(CONFIG_URL, {
|
return fetch(CONFIG_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||||
body: JSON.stringify({ tmdb_api_key: t ? t.value : '', tvdb_api_key: v ? v.value : '' })
|
body: JSON.stringify({ tmdb_api_key: t ? t.value : '', tvdb_api_key: v ? v.value : '' })
|
||||||
}).catch(function () { /* ignore */ });
|
}).catch(function () { /* ignore */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toast(msg, type) {
|
||||||
|
if (typeof showToast === 'function') showToast(msg, type); // shared shell helper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors music's testConnection(): save the key, then hit the test
|
||||||
|
// endpoint, then toast the result. Isolated -> /api/video/enrichment/<svc>/test.
|
||||||
|
function testConnection(svc) {
|
||||||
|
var name = svc.toUpperCase();
|
||||||
|
toast('Testing ' + name + ' connection…', 'info');
|
||||||
|
saveKeys().then(function () {
|
||||||
|
return fetch('/api/video/enrichment/' + svc + '/test',
|
||||||
|
{ method: 'POST', headers: { 'Accept': 'application/json' } });
|
||||||
|
}).then(function (r) { return r.json(); }).then(function (res) {
|
||||||
|
if (res && res.success) toast(res.message || (name + ' connection successful'), 'success');
|
||||||
|
else toast(name + ' connection failed: ' + ((res && res.error) || 'unknown'), 'error');
|
||||||
|
}).catch(function () { toast('Failed to test ' + name + ' connection', 'error'); });
|
||||||
|
}
|
||||||
|
|
||||||
function onPageShown(e) {
|
function onPageShown(e) {
|
||||||
if (e && e.detail !== PAGE_ID) return;
|
if (e && e.detail !== PAGE_ID) return;
|
||||||
load();
|
load();
|
||||||
|
|
@ -105,6 +123,15 @@
|
||||||
var el = document.getElementById(id);
|
var el = document.getElementById(id);
|
||||||
if (el) el.addEventListener('change', saveKeys);
|
if (el) el.addEventListener('change', saveKeys);
|
||||||
});
|
});
|
||||||
|
// Per-connection Test buttons (same behaviour as music's testConnection).
|
||||||
|
var testBtns = document.querySelectorAll('[data-video-test-service]');
|
||||||
|
for (var k = 0; k < testBtns.length; k++) {
|
||||||
|
(function (b) {
|
||||||
|
b.addEventListener('click', function () {
|
||||||
|
testConnection(b.getAttribute('data-video-test-service'));
|
||||||
|
});
|
||||||
|
})(testBtns[k]);
|
||||||
|
}
|
||||||
document.addEventListener('soulsync:video-page-shown', onPageShown);
|
document.addEventListener('soulsync:video-page-shown', onPageShown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue