+38
-17
src/dashboard.zig
+38
-17
src/dashboard.zig
···
1
1
const std = @import("std");
2
-
const posix = std.posix;
3
2
const stats = @import("stats.zig");
4
3
const db = @import("db.zig");
5
-
6
-
const DEFAULT_TURBOSTREAM = "api.graze.social";
7
4
8
5
fn formatNumber(buf: []u8, n: u64) []const u8 {
9
6
var temp: [32]u8 = undefined;
···
32
29
const lag_ms = s.getPostLagMs();
33
30
const lag_sec = @divTrunc(lag_ms, 1000);
34
31
const status = s.getStatus();
35
-
const turbostream_host = posix.getenv("TURBOSTREAM_HOST") orelse DEFAULT_TURBOSTREAM;
36
32
37
33
var buf: std.ArrayList(u8) = .{};
38
34
const w = buf.writer(alloc);
···
100
96
\\ }
101
97
\\ .stat-value { font-size: 18px; color: #fff; }
102
98
\\ .stat-label { font-size: 11px; color: #444; }
103
-
\\ .criteria {
99
+
\\ .criteria, .platforms {
104
100
\\ margin-bottom: 1.5rem;
105
101
\\ font-size: 12px;
106
102
\\ }
107
103
\\ .criteria-title { color: #444; margin-bottom: 0.25rem; }
108
104
\\ .criteria-list { color: #666; }
105
+
\\ .platform-bars { margin-top: 0.5rem; }
106
+
\\ .platform-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.3rem; }
107
+
\\ .platform-name { width: 80px; color: #666; }
108
+
\\ .bar { flex: 1; height: 8px; background: #1a1a1a; border-radius: 4px; overflow: hidden; }
109
+
\\ .bar-fill { height: 100%; border-radius: 4px; }
110
+
\\ .platform-pct { width: 32px; text-align: right; color: #555; font-size: 11px; }
109
111
\\ footer {
110
112
\\ padding-top: 1rem;
111
113
\\ border-top: 1px solid #1a1a1a;
···
159
161
\\ </div>
160
162
\\ </div>
161
163
\\
162
-
\\ <div class="criteria">
163
-
\\ <div class="criteria-title">included</div>
164
-
\\ <div class="criteria-list">soundcloud, bandcamp, spotify, plyr.fm links</div>
164
+
\\ <div class="platforms">
165
+
\\ <div class="criteria-title">platforms</div>
166
+
\\ <div class="platform-bars">
167
+
);
168
+
169
+
// get platform counts
170
+
const platforms = s.getPlatformCounts();
171
+
const total = platforms.soundcloud + platforms.bandcamp + platforms.spotify + platforms.plyr;
172
+
173
+
if (total > 0) {
174
+
// calculate percentages
175
+
const sc_pct = (platforms.soundcloud * 100) / total;
176
+
const bc_pct = (platforms.bandcamp * 100) / total;
177
+
const sp_pct = (platforms.spotify * 100) / total;
178
+
const pl_pct = (platforms.plyr * 100) / total;
179
+
180
+
try w.print(
181
+
\\ <div class="platform-row"><span class="platform-name">soundcloud</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#ff5500"></div></div><span class="platform-pct">{d}%</span></div>
182
+
\\ <div class="platform-row"><span class="platform-name">bandcamp</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#1da0c3"></div></div><span class="platform-pct">{d}%</span></div>
183
+
\\ <div class="platform-row"><span class="platform-name">spotify</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#1db954"></div></div><span class="platform-pct">{d}%</span></div>
184
+
\\ <div class="platform-row"><span class="platform-name">plyr.fm</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#7c3aed"></div></div><span class="platform-pct">{d}%</span></div>
185
+
, .{ sc_pct, sc_pct, bc_pct, bc_pct, sp_pct, sp_pct, pl_pct, pl_pct });
186
+
} else {
187
+
try w.writeAll(
188
+
\\ <div class="platform-row"><span class="criteria-list">collecting data...</span></div>
189
+
);
190
+
}
191
+
192
+
try w.writeAll(
193
+
\\ </div>
165
194
\\ </div>
166
195
\\
167
196
\\ <div class="criteria">
···
171
200
\\
172
201
\\ <footer>
173
202
\\ <a href="https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed">source</a>
174
-
\\ <span>relay: <a href="https://
175
-
);
176
-
try w.writeAll(turbostream_host);
177
-
try w.writeAll(
178
-
\\" target="_blank">
179
-
);
180
-
try w.writeAll(turbostream_host);
181
-
try w.writeAll(
182
-
\\</a></span>
203
+
\\ <span>stream: <a href="https://graze.social" target="_blank">turbostream</a></span>
183
204
\\ </footer>
184
205
\\ </div>
185
206
\\
+56
src/filter.zig
+56
src/filter.zig
···
1
1
const std = @import("std");
2
2
const mem = std.mem;
3
3
const json = std.json;
4
+
const stats = @import("stats.zig");
4
5
5
6
const Record = json.ObjectMap;
6
7
const Filter = *const fn (Record) ?bool;
···
116
117
if (mem.indexOf(u8, uri, domain) != null) return true;
117
118
}
118
119
return false;
120
+
}
121
+
122
+
/// detect which platform a URL belongs to
123
+
pub fn detectPlatform(uri: []const u8) ?stats.Platform {
124
+
if (mem.indexOf(u8, uri, "soundcloud.com") != null or
125
+
mem.indexOf(u8, uri, "on.soundcloud.com") != null)
126
+
{
127
+
return .soundcloud;
128
+
}
129
+
if (mem.indexOf(u8, uri, "bandcamp.com") != null) return .bandcamp;
130
+
if (mem.indexOf(u8, uri, "open.spotify.com") != null or
131
+
mem.indexOf(u8, uri, "spotify.link") != null)
132
+
{
133
+
return .spotify;
134
+
}
135
+
if (mem.indexOf(u8, uri, "plyr.fm") != null) return .plyr;
136
+
return null;
137
+
}
138
+
139
+
/// detect platform from post record (checks facets and embeds)
140
+
pub fn detectPlatformFromRecord(record: Record) ?stats.Platform {
141
+
// check facets
142
+
if (record.get("facets")) |facets_val| {
143
+
if (facets_val == .array) {
144
+
for (facets_val.array.items) |facet| {
145
+
if (facet != .object) continue;
146
+
const features_val = facet.object.get("features") orelse continue;
147
+
if (features_val != .array) continue;
148
+
149
+
for (features_val.array.items) |feature| {
150
+
if (feature != .object) continue;
151
+
const uri_val = feature.object.get("uri") orelse continue;
152
+
if (uri_val != .string) continue;
153
+
if (detectPlatform(uri_val.string)) |p| return p;
154
+
}
155
+
}
156
+
}
157
+
}
158
+
159
+
// check embed
160
+
if (record.get("embed")) |embed_val| {
161
+
if (embed_val == .object) {
162
+
if (embed_val.object.get("external")) |external_val| {
163
+
if (external_val == .object) {
164
+
if (external_val.object.get("uri")) |uri_val| {
165
+
if (uri_val == .string) {
166
+
if (detectPlatform(uri_val.string)) |p| return p;
167
+
}
168
+
}
169
+
}
170
+
}
171
+
}
172
+
}
173
+
174
+
return null;
119
175
}
120
176
121
177
/// simple text-based check for music links (used for backfill)
+6
src/jetstream.zig
+6
src/jetstream.zig
···
165
165
};
166
166
167
167
stats.get().recordMatch();
168
+
169
+
// track platform
170
+
if (filter.detectPlatformFromRecord(post_record.object)) |platform| {
171
+
stats.get().recordPlatform(platform);
172
+
}
173
+
168
174
std.debug.print("added: {s}\n", .{at_uri.string});
169
175
}
170
176
};
+30
src/stats.zig
+30
src/stats.zig
···
6
6
7
7
const STATS_PATH = "/data/stats.json";
8
8
9
+
pub const Platform = enum { soundcloud, bandcamp, spotify, plyr };
10
+
9
11
pub const Stats = struct {
10
12
started_at: i64,
11
13
prior_uptime: u64 = 0,
···
17
19
connected_at: Atomic(i64),
18
20
last_post_time_ms: Atomic(i64), // actual post creation time from TID
19
21
22
+
// platform counters
23
+
soundcloud: Atomic(u64),
24
+
bandcamp: Atomic(u64),
25
+
spotify: Atomic(u64),
26
+
plyr: Atomic(u64),
27
+
20
28
// for lag trend tracking
21
29
prev_lag_ms: Atomic(i64),
22
30
···
30
38
.last_match_time = Atomic(i64).init(0),
31
39
.connected_at = Atomic(i64).init(0),
32
40
.last_post_time_ms = Atomic(i64).init(0),
41
+
.soundcloud = Atomic(u64).init(0),
42
+
.bandcamp = Atomic(u64).init(0),
43
+
.spotify = Atomic(u64).init(0),
44
+
.plyr = Atomic(u64).init(0),
33
45
.prev_lag_ms = Atomic(i64).init(0),
34
46
};
35
47
self.load();
···
110
122
pub fn recordMatch(self: *Stats) void {
111
123
_ = self.matches.fetchAdd(1, .monotonic);
112
124
self.last_match_time.store(std.time.milliTimestamp(), .monotonic);
125
+
}
126
+
127
+
pub fn recordPlatform(self: *Stats, platform: Platform) void {
128
+
switch (platform) {
129
+
.soundcloud => _ = self.soundcloud.fetchAdd(1, .monotonic),
130
+
.bandcamp => _ = self.bandcamp.fetchAdd(1, .monotonic),
131
+
.spotify => _ = self.spotify.fetchAdd(1, .monotonic),
132
+
.plyr => _ = self.plyr.fetchAdd(1, .monotonic),
133
+
}
134
+
}
135
+
136
+
pub fn getPlatformCounts(self: *const Stats) struct { soundcloud: u64, bandcamp: u64, spotify: u64, plyr: u64 } {
137
+
return .{
138
+
.soundcloud = self.soundcloud.load(.monotonic),
139
+
.bandcamp = self.bandcamp.load(.monotonic),
140
+
.spotify = self.spotify.load(.monotonic),
141
+
.plyr = self.plyr.load(.monotonic),
142
+
};
113
143
}
114
144
115
145
pub fn recordConnected(self: *Stats) void {