@@ -24,7 +24,8 @@ const state = {
2424 result : null
2525 } ,
2626 selectedSetup : "local" ,
27- savedProfile : null
27+ savedProfile : null ,
28+ community : { profiles : [ ] , status : "offline" , mode : "local" , publishing : "local-only" }
2829} ;
2930
3031const demoSetups = [
@@ -68,13 +69,46 @@ const clamp = (n, min = 0, max = 100) => Math.max(min, Math.min(max, Number(n) |
6869const pct = ( n ) => `${ Math . round ( clamp ( n ) ) } %` ;
6970const mb = ( n ) => `${ Math . round ( Number ( n ) || 0 ) } MB` ;
7071const gb = ( n ) => `${ Number ( n || 0 ) . toFixed ( 1 ) } GB` ;
71- const esc = ( value ) => String ( value ?? "-" ) . replace ( / [ & < > " ' ] / g, ( ch ) => ( {
72+ const isMissing = ( value ) => value === null || value === undefined || value === "" || value === "null" || value === "undefined" ;
73+ const prettyValue = ( value , fallback = "-" ) => {
74+ if ( isMissing ( value ) ) return fallback ;
75+ if ( typeof value === "number" ) return Number . isFinite ( value ) ? String ( value ) : fallback ;
76+ if ( typeof value === "boolean" ) return value ? "yes" : "no" ;
77+ if ( Array . isArray ( value ) ) {
78+ const text = value . map ( ( item ) => prettyValue ( item , "" ) ) . filter ( Boolean ) . join ( ", " ) ;
79+ return text || fallback ;
80+ }
81+ if ( typeof value === "object" ) {
82+ const preferred = value . name ?? value . label ?? value . caption ?? value . value ?? value . status ?? value . id ;
83+ if ( ! isMissing ( preferred ) ) return prettyValue ( preferred , fallback ) ;
84+ try {
85+ return JSON . stringify ( value ) . replace ( / [ { } " ] / g, "" ) . slice ( 0 , 180 ) || fallback ;
86+ } catch {
87+ return fallback ;
88+ }
89+ }
90+ const text = String ( value ) . trim ( ) ;
91+ if ( ! text || text === "[object Object]" ) return fallback ;
92+ return text ;
93+ } ;
94+ const esc = ( value ) => prettyValue ( value ) . replace ( / [ & < > " ' ] / g, ( ch ) => ( {
7295 "&" : "&" ,
7396 "<" : "<" ,
7497 ">" : ">" ,
7598 "\"" : """ ,
7699 "'" : "'"
77100} [ ch ] ) ) ;
101+ const safeEsc = ( value , fallback = "-" ) => esc ( prettyValue ( value , fallback ) ) ;
102+
103+ async function parseApiError ( response ) {
104+ const text = await response . text ( ) ;
105+ try {
106+ const payload = JSON . parse ( text ) ;
107+ return prettyValue ( payload . error || payload . message || text , "не удалось" ) ;
108+ } catch {
109+ return prettyValue ( text , "не удалось" ) ;
110+ }
111+ }
78112
79113function setBar ( id , value ) {
80114 const el = $ ( id ) ;
@@ -412,7 +446,7 @@ async function startCpuStress(durationSec) {
412446 headers : { "Content-Type" : "application/json" } ,
413447 body : JSON . stringify ( { cpu : true , memory : false , durationSec, workers : threads } )
414448 } ) ;
415- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
449+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
416450 const result = await response . json ( ) ;
417451 const status = result . started ?. cpu || { } ;
418452 state . stress . serverCpu = true ;
@@ -427,7 +461,7 @@ async function startServerStress(options) {
427461 headers : { "Content-Type" : "application/json" } ,
428462 body : JSON . stringify ( { ...options , workers : threads } )
429463 } ) ;
430- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
464+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
431465 const result = await response . json ( ) ;
432466 if ( result . started ?. cpu ) {
433467 state . stress . serverCpu = true ;
@@ -717,17 +751,17 @@ function localProfile() {
717751 const score = calculateRigScore ( ) . score || state . savedProfile ?. score || 0 ;
718752 return {
719753 id : "local" ,
720- name : `${ inv . gpu ?. name || "Local" } Rig` ,
721- owner : inv . system ?. user || "you" ,
754+ name : `${ prettyValue ( inv . gpu ?. name , "Local" ) } Rig` ,
755+ owner : prettyValue ( inv . system ?. user , "you" ) ,
722756 score,
723- cpu : inv . cpu ?. name || "-" ,
724- gpu : inv . gpu ?. name || "-" ,
725- memory : `${ inv . memory ?. totalGb || "-" } GB` ,
757+ cpu : prettyValue ( inv . cpu ?. name ) ,
758+ gpu : prettyValue ( inv . gpu ?. name ) ,
759+ memory : `${ prettyValue ( inv . memory ?. totalGb ) } GB` ,
726760 storage : `${ inv . physicalDisks ?. length || 0 } drives` ,
727- board : inv . board ?. product || "-" ,
728- os : inv . os ?. caption || "-" ,
761+ board : prettyValue ( inv . board ?. product ) ,
762+ os : prettyValue ( inv . os ?. caption ) ,
729763 bench : {
730- cpu : state . bench . cpu ?. score || "-" ,
764+ cpu : prettyValue ( state . bench . cpu ?. score ) ,
731765 memory : state . bench . memory ? `${ state . bench . memory . gbps } GB/s` : "-" ,
732766 gpu : state . bench . gpu ? `${ state . bench . gpu . fps } fps` : "-" ,
733767 sensors : state . bench . sensors ? sensorLine ( state . bench . sensors ) : "-"
@@ -738,15 +772,25 @@ function localProfile() {
738772
739773function setupProfiles ( ) {
740774 const saved = state . savedProfile ? [ state . savedProfile ] : [ ] ;
741- return [ localProfile ( ) , ...saved . filter ( ( p ) => p . id !== "local-saved" ) , ...demoSetups ] ;
775+ const remote = state . community ?. profiles || [ ] ;
776+ const seen = new Set ( ) ;
777+ return [ localProfile ( ) , ...saved . filter ( ( p ) => p . id !== "local-saved" ) , ...remote , ...demoSetups ]
778+ . filter ( ( profile ) => {
779+ const id = prettyValue ( profile . id , profile . name || "setup" ) ;
780+ if ( seen . has ( id ) ) return false ;
781+ seen . add ( id ) ;
782+ return true ;
783+ } ) ;
742784}
743785
744786function renderCommunity ( ) {
745787 if ( ! state . snapshot ) return ;
746788 const profiles = setupProfiles ( ) . sort ( ( a , b ) => Number ( b . score || 0 ) - Number ( a . score || 0 ) ) ;
747789 const local = localProfile ( ) ;
748790 $ ( "localSetupName" ) . textContent = local . name ;
749- $ ( "localSetupMeta" ) . textContent = local . score ? `RigScore ${ local . score } · ${ local . cpu } ` : `${ local . cpu } · run Lab tests for a public score` ;
791+ $ ( "localSetupMeta" ) . textContent = local . score
792+ ? `RigScore ${ local . score } · ${ local . cpu } · sync ${ state . community ?. status || "local" } `
793+ : `${ local . cpu } · run Lab tests for a public score` ;
750794 $ ( "setupCards" ) . innerHTML = profiles . map ( ( profile ) => `
751795 <button class="setup-card ${ state . selectedSetup === profile . id ? "active" : "" } " data-setup-id="${ esc ( profile . id ) } ">
752796 <span>${ esc ( profile . owner ) } </span>
@@ -782,9 +826,23 @@ function renderCommunity() {
782826 ] ) ;
783827}
784828
785- function saveLocalProfile ( ) {
829+ async function saveLocalProfile ( ) {
786830 state . savedProfile = { ...localProfile ( ) , id : "local-saved" , name : "Saved Local Snapshot" } ;
787831 localStorage . setItem ( "rigscope.profile" , JSON . stringify ( state . savedProfile ) ) ;
832+ try {
833+ const response = await fetch ( "/api/community/profile" , {
834+ method : "POST" ,
835+ headers : { "Content-Type" : "application/json" } ,
836+ body : JSON . stringify ( state . savedProfile )
837+ } ) ;
838+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
839+ const result = await response . json ( ) ;
840+ state . community . status = result . github || result . status || "saved locally" ;
841+ await loadCommunity ( ) ;
842+ } catch ( error ) {
843+ state . community . status = "не удалось синхронизировать" ;
844+ console . error ( error ) ;
845+ }
788846 renderCommunity ( ) ;
789847}
790848
@@ -1143,11 +1201,11 @@ function update(snapshot) {
11431201async function refresh ( ) {
11441202 try {
11451203 const response = await fetch ( "/api/snapshot" , { cache : "no-store" } ) ;
1146- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1204+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
11471205 update ( await response . json ( ) ) ;
11481206 $ ( "livePulse" ) . style . background = "var(--green)" ;
11491207 } catch ( error ) {
1150- $ ( "updatedAt" ) . textContent = "telemetry error " ;
1208+ $ ( "updatedAt" ) . textContent = "не удалось загрузить телеметрию " ;
11511209 $ ( "livePulse" ) . style . background = "var(--red)" ;
11521210 console . error ( error ) ;
11531211 }
@@ -1156,7 +1214,7 @@ async function refresh() {
11561214async function loadToolkit ( ) {
11571215 try {
11581216 const response = await fetch ( "/api/toolkit" , { cache : "no-store" } ) ;
1159- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1217+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
11601218 state . toolkit = await response . json ( ) ;
11611219 if ( state . snapshot ) {
11621220 renderToolkit ( ) ;
@@ -1171,7 +1229,7 @@ async function loadToolkit() {
11711229async function loadNativeRunners ( ) {
11721230 try {
11731231 const response = await fetch ( "/api/native-runners" , { cache : "no-store" } ) ;
1174- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1232+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
11751233 const payload = await response . json ( ) ;
11761234 state . nativeRunners = payload ;
11771235 state . nativeRunnerStatus = payload . status ;
@@ -1182,6 +1240,19 @@ async function loadNativeRunners() {
11821240 }
11831241}
11841242
1243+ async function loadCommunity ( ) {
1244+ try {
1245+ const response = await fetch ( "/api/community" , { cache : "no-store" } ) ;
1246+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
1247+ state . community = await response . json ( ) ;
1248+ if ( state . snapshot ) renderCommunity ( ) ;
1249+ } catch ( error ) {
1250+ state . community = { profiles : [ ] , status : "не удалось загрузить" , mode : "local" , publishing : "local-only" } ;
1251+ if ( state . snapshot ) renderCommunity ( ) ;
1252+ console . error ( error ) ;
1253+ }
1254+ }
1255+
11851256async function pollNativeRunner ( ) {
11861257 try {
11871258 const response = await fetch ( "/api/native-runners/status" , { cache : "no-store" } ) ;
@@ -1216,7 +1287,7 @@ async function startNativeRunner() {
12161287 acknowledgement
12171288 } )
12181289 } ) ;
1219- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1290+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
12201291 state . nativeRunnerStatus = await response . json ( ) ;
12211292 renderNativeRunners ( ) ;
12221293 if ( ! state . nativeRunnerTimer ) state . nativeRunnerTimer = setInterval ( pollNativeRunner , 1500 ) ;
@@ -1229,7 +1300,7 @@ async function startNativeRunner() {
12291300async function stopNativeRunner ( ) {
12301301 try {
12311302 const response = await fetch ( "/api/native-runners/stop" , { method : "POST" , cache : "no-store" } ) ;
1232- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1303+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
12331304 state . nativeRunnerStatus = await response . json ( ) ;
12341305 renderNativeRunners ( ) ;
12351306 } catch ( error ) {
@@ -1257,14 +1328,14 @@ async function runServerBench(type, url, buttonId, statusId, runningText) {
12571328 $ ( statusId ) . textContent = runningText ;
12581329 try {
12591330 const response = await fetch ( url , { method : "POST" , cache : "no-store" } ) ;
1260- if ( ! response . ok ) throw new Error ( await response . text ( ) ) ;
1331+ if ( ! response . ok ) throw new Error ( await parseApiError ( response ) ) ;
12611332 state . bench [ type ] = await response . json ( ) ;
12621333 if ( state . snapshot ) {
12631334 renderSuite ( state . snapshot ) ;
12641335 renderLab ( state . snapshot ) ;
12651336 }
12661337 } catch ( error ) {
1267- $ ( statusId ) . textContent = "Benchmark error" ;
1338+ $ ( statusId ) . textContent = `Не удалось: ${ prettyValue ( error . message , "ошибка теста" ) } ` ;
12681339 console . error ( error ) ;
12691340 } finally {
12701341 button . disabled = false ;
@@ -1347,5 +1418,7 @@ try {
13471418refresh ( ) ;
13481419loadToolkit ( ) ;
13491420loadNativeRunners ( ) ;
1421+ loadCommunity ( ) ;
13501422setView ( location . hash . slice ( 1 ) || "overview" ) ;
13511423setInterval ( refresh , 7000 ) ;
1424+ setInterval ( loadCommunity , 60000 ) ;
0 commit comments