gbfs worker splitbrain via parent + basic gtfs-rt support trip-update only for now + fix for the 11pm problem via conditionals in piped queries, keeping the atom based one for a bit for lols

d7b8847 · gmorell · 2022-10-17 03:53

22 files +446 -100

Files changed

modified .idea/misc.xml
+1 −1
@@ -3,7 +3,7 @@
3 3 <component name="FrameworkDetectionExcludesConfiguration">
4 4 <file type="MicroPython" url="file://$PROJECT_DIR$/clients" />
5 5 </component>
6 − <component name="ProjectRootManager" version="2" languageLevel="JDK_18">
6 + <component name="ProjectRootManager" version="2" languageLevel="JDK_18" project-jdk-name="Elixir at /usr/lib/elixir" project-jdk-type="Elixir SDK">
7 7 <output url="file://$PROJECT_DIR$/classes" />
8 8 </component>
9 9 </project>
modified apollos_crib.iml
+1 −2
@@ -2,7 +2,7 @@
2 2 <module type="ELIXIR_MODULE" version="4">
3 3 <component name="FacetManager">
4 4 <facet type="Python" name="Python">
5 − <configuration sdkName="Python 3.8" />
5 + <configuration sdkName="" />
6 6 </facet>
7 7 </component>
8 8 <component name="NewModuleRootManager">
@@ -98,6 +98,5 @@
98 98 <excludeFolder url="file://$MODULE_DIR$/_build/dev/lib/credentials_obfuscation" />
99 99 </content>
100 100 <orderEntry type="sourceFolder" forTests="false" />
101 − <orderEntry type="library" name="Python 3.8 interpreter library" level="application" />
102 101 </component>
103 102 </module>
modified apps/room_gbfs/lib/room_gbfs/gbfs_worker.ex
+2 −2
@@ -15,7 +15,7 @@ defmodule RoomGbfs.Worker do
15 15 end
16 16
17 17 def init(opts) do
18 − {:ok, %{id: opts[:name]}}
18 + {:ok, %{id: opts[:name], inst: nil}}
19 19 end
20 20
21 21 # public
@@ -47,7 +47,7 @@ defmodule RoomGbfs.Worker do
47 47
48 48 def handle_cast(:refresh_db_cfg, state) do
49 49 IO.puts("rerere")
50 − {:noreply, state}
50 + {:noreply, state |> Map.put(:inst, nil)}
51 51 end
52 52
53 53 defp bcast(id, file, complete, total) do
modified apps/room_gtfs/lib/room_gtfs/gtfs_worker.ex
+229 −12
@@ -1,6 +1,6 @@
1 1 defmodule RoomGtfs.Worker do
2 2 @moduledoc false
3 − use GenServer
3 + use Parent.GenServer
4 4
5 5 require Logger
6 6
@@ -11,7 +11,7 @@ defmodule RoomGtfs.Worker do
11 11 @registry :zeus
12 12
13 13 def start_link(opts) do
14 − GenServer.start_link(__MODULE__, opts, name: via_tuple("gtfs" <> opts[:name]))
14 + Parent.GenServer.start_link(__MODULE__, opts, name: via_tuple("gtfs" <> opts[:name]))
15 15 end
16 16
17 17 # Public
@@ -33,29 +33,252 @@ defmodule RoomGtfs.Worker do
33 33 |> GenServer.cast(:update_realtime)
34 34 end
35 35
36 + def get_current_realtime(name, trips, stop) do
37 + "gtfs#{name}"
38 + |> via_tuple
39 + |> GenServer.call({:query_realtime, trips, stop})
40 + end
41 +
36 42 def query_stop(id, query) do
37 − # Simple implementation for now, gtfs-rt bs later
38 − Storage.get_upcoming_arrivals_for_stop(id, query.stop)
43 + inst = Configuration.get_source!(id)
44 + res = Storage.get_upcoming_arrivals_for_stop(id, query.stop)
45 + case inst.config.url_rt_tu do
46 + nil -> res
47 + _val -> trips = res |> Enum.map(fn x -> x.trip_id end)
48 +
49 + case get_current_realtime(id, trips, query.stop) do
50 + [] ->
51 + res
52 +
53 + rtvals ->
54 + res
55 + |> Enum.map(fn x ->
56 + case Enum.find(rtvals, fn v -> x.trip_id == v.trip_update.trip.trip_id end) do
57 + nil ->
58 + x
59 +
60 + val ->
61 + case val.trip_update.stop_time_update.arrival do
62 + nil ->
63 + x
64 +
65 + _arrival ->
66 + x
67 + |> Map.put(:arrival_time_live_ts, val.trip_update.stop_time_update.arrival.time)
68 + |> Map.put(
69 + :arrival_time_live_delay,
70 + val.trip_update.stop_time_update.arrival.delay
71 + )
72 + |> Map.put(
73 + :arrival_time_live_uncertianty,
74 + val.trip_update.stop_time_update.arrival.uncertainty
75 + )
76 + end
77 + end
78 + end)
79 + end
80 + end
81 +
39 82 end
40 83
41 84 # etc
42 85 def init(opts) do
43 − {:ok, %{id: opts[:name]}}
86 + Periodic.start_link(
87 + every: :timer.seconds(4),
88 + run: fn -> RoomGtfs.Worker.refresh_db_cfg(opts[:name]) end,
89 + initial_delay: 10
90 + )
91 +
92 + {:ok, child_rt} = Parent.start_child({RoomGtfs.Worker.RT, opts})
93 + {:ok, child_static} = Parent.start_child({RoomGtfs.Worker.Static, opts})
94 +
95 + {:ok,
96 + %{
97 + id: opts[:name],
98 + inst: nil,
99 + child_rt: child_rt,
100 + child_static: child_static
101 + }}
102 + end
103 +
104 + def handle_cast(:refresh_db_cfg, state) do
105 + inst = Configuration.get_source!(state.id)
106 + {:noreply, state |> Map.put(:inst, inst)}
107 + end
108 +
109 + def handle_cast(:update_static, state) do
110 + GenServer.cast(state.child_static, :update_static)
111 + {:noreply, state}
112 + end
113 +
114 + def handle_cast(:update_realtime, state) do
115 + GenServer.cast(state.child_rt, :update_realtime)
116 + {:noreply, state}
117 + end
118 +
119 + def handle_cast(_msg, state) do
120 + {:noreply, state}
121 + end
122 +
123 + def handle_call({:query_realtime, trips, stop}, _from, state) do
124 + r = GenServer.call(state.child_rt, {:query_realtime, trips, stop})
125 + {:reply, r, state}
44 126 end
45 127
46 128 def handle_call(_msg, _from, state) do
47 129 {:reply, :ok, state}
48 130 end
49 131
132 + defp via_tuple(name), do: {:via, Registry, {@registry, name}}
133 +end
134 +
135 +defmodule RoomGtfs.Worker.RT do
136 + use GenServer
137 + @registry :zeus
138 +
139 + alias RoomSanctum.Configuration
140 + alias RoomSanctum.Storage
141 + alias RoomSanctum.Repo
142 +
143 + def start_link(opts) do
144 + GenServer.start_link(__MODULE__, opts, name: via_tuple("gtfs-rt" <> opts[:name]))
145 + end
146 +
147 + def update_realtime_data(name) do
148 + "gtfs-rt#{name}"
149 + |> via_tuple()
150 + |> GenServer.cast(:update_realtime)
151 + end
152 +
153 + def refresh_db_cfg(name) do
154 + "gtfs-rt#{name}"
155 + |> via_tuple()
156 + |> GenServer.cast(:refresh_db_cfg)
157 + end
158 +
159 + def init(opts) do
160 + Periodic.start_link(
161 + every: :timer.seconds(4),
162 + run: fn -> RoomGtfs.Worker.RT.refresh_db_cfg(opts[:name]) end,
163 + initial_delay: 10
164 + )
165 +
166 + Periodic.start_link(
167 + every: :timer.seconds(30),
168 + run: fn -> RoomGtfs.Worker.RT.update_realtime_data(opts[:name]) end,
169 + initial_delay: 100
170 + )
171 +
172 + {:ok,
173 + %{
174 + id: opts[:name],
175 + inst: nil,
176 + rt_sa: nil,
177 + rt_tu: nil,
178 + rt_vp: nil
179 + }}
180 + end
181 +
182 + defp via_tuple(name), do: {:via, Registry, {@registry, name}}
183 +
184 + def fetch_rt_url(url) do
185 + case HTTPoison.get(url, [], follow_redirect: true) do
186 + {:ok, result} -> {:ok, result.body |> TransitRealtime.FeedMessage.decode()}
187 + {:error, error} -> {:error, error}
188 + end
189 + end
190 +
50 191 def handle_cast(:refresh_db_cfg, state) do
51 − IO.puts("rerere")
192 + inst = Configuration.get_source!(state.id)
193 + {:noreply, state |> Map.put(:inst, inst)}
194 + end
195 +
196 + def handle_cast(:update_realtime, state) do
197 + state =
198 + case state.inst.config |> Map.get(:url_rt_sa) do
199 + nil ->
200 + state
201 +
202 + val ->
203 + case fetch_rt_url(val) do
204 + {:ok, data_sa} -> state |> Map.put(:rt_sa, data_sa)
205 + {:error, _error} -> state
206 + end
207 + end
208 +
209 + state =
210 + case state.inst.config |> Map.get(:url_rt_tu) do
211 + nil ->
212 + state
213 +
214 + val ->
215 + case fetch_rt_url(val) do
216 + {:ok, data_tu} -> state |> Map.put(:rt_tu, data_tu)
217 + {:error, _error} -> state
218 + end
219 + end
220 +
221 + state =
222 + case state.inst.config |> Map.get(:url_rt_vp) do
223 + nil ->
224 + state
225 +
226 + val ->
227 + case fetch_rt_url(val) do
228 + {:ok, data_vp} -> state |> Map.put(:rt_vp, data_vp)
229 + {:error, _error} -> state
230 + end
231 + end
232 +
52 233 {:noreply, state}
53 234 end
54 235
236 + def handle_call({:query_realtime, trips, stop}, _from, state) do
237 +# IO.inspect(trips)
238 +
239 + # filter out the protobuf for all relevant trips and then the relevant stop on that trip, nice and small
240 + relevant_trips =
241 + state.rt_tu.entity
242 + |> Enum.filter(fn x -> Enum.member?(trips, x.trip_update.trip.trip_id) end)
243 + |> Enum.map(fn x ->
244 + x
245 + |> Kernel.put_in(
246 + [Access.key(:trip_update, %{}), Access.key(:stop_time_update, %{})],
247 + x.trip_update.stop_time_update
248 + |> Enum.filter(fn x -> x.stop_id == stop end)
249 + |> List.first()
250 + )
251 + end)
252 + {:reply, relevant_trips, state}
253 + end
254 +end
255 +
256 +defmodule RoomGtfs.Worker.Static do
257 + use GenServer
258 + require Logger
259 + @registry :zeus
260 +
261 + alias RoomSanctum.Configuration
262 + alias RoomSanctum.Storage
263 + alias RoomSanctum.Repo
264 +
265 + def start_link(opts) do
266 + GenServer.start_link(__MODULE__, opts, name: via_tuple("gtfs-st" <> opts[:name]))
267 + end
268 +
55 269 defp bcast(id, file, complete, total) do
56 270 Phoenix.PubSub.broadcast(RoomSanctum.PubSub, "gtfs", {:gtfs, id, file, complete, total})
57 271 end
58 272
273 + def init(opts) do
274 + {:ok,
275 + %{
276 + id: opts[:name]
277 + }}
278 + end
279 +
280 + defp via_tuple(name), do: {:via, Registry, {@registry, name}}
281 +
59 282 defp write_file(contents, type, id) do
60 283 datetime = NaiveDateTime.local_now()
61 284 Logger.info("GTFS::#{id} writing #{type}")
@@ -238,10 +461,4 @@ defmodule RoomGtfs.Worker do
238 461
239 462 {:noreply, state}
240 463 end
241 −
242 − def handle_cast(_msg, state) do
243 − {:noreply, state}
244 − end
245 −
246 − defp via_tuple(name), do: {:via, Registry, {@registry, name}}
247 464 end
modified apps/room_sanctum/lib/room_sanctum/condensers/basic_mqtt.ex
+22 −7
@@ -26,9 +26,17 @@ defmodule RoomSanctum.Condenser.BasicMQTT do
26 26 datestr |> Timex.parse!("{ISO:Extended}") |> Timex.format!( "%H:%M", :strftime)
27 27 end
28 28
29 + defp livetime(unix, _tz) when is_nil(unix) do
30 + unix
31 + end
32 +
33 + defp livetime(unix, tz) do
34 + unix |> DateTime.from_unix! |> Timex.Timezone.convert(tz) |> DateTime.to_time
35 + end
36 +
29 37 def condense({_id, type}, data) do
30 −# if type == :tidal do
31 −# IO.inspect({id, type, data})
38 +# if type == :gtfs do
39 +# IO.inspect({type, data})
32 40 # end
33 41
34 42 case type do
@@ -37,19 +45,26 @@ defmodule RoomSanctum.Condenser.BasicMQTT do
37 45 |> Enum.map(fn f ->
38 46 %{
39 47 time: f.arrival_time,
48 + time_live: f.arrival_time_live_ts,
40 49 destination: f.trip.trip_headsign,
41 50 direction: f.trip.direction.direction,
42 51 route: f.trip.route_id,
43 − mode: f.trip.route.route_type |> gtfs_mode
52 + mode: f.trip.route.route_type |> gtfs_mode,
53 + tz: f.tz
44 54 }
45 55 end)
46 − |> Enum.reduce(%{}, fn %{time: time, destination: dest, direction: dir, route: route, mode: mode}, acc ->
56 + |> Enum.reduce(%{}, fn %{time: time, time_live: time_live, destination: dest, direction: dir, route: route, mode: mode, tz: tz}, acc ->
47 57 update_in(acc, [{route, dest, dir}], fn
48 − nil -> %{route: route, dest: dest, dir: dir, mode: mode, times: [time]}
49 − refs -> %{refs | times: [time | refs.times]}
58 + nil -> %{route: route, dest: dest, dir: dir, mode: mode, times: [time], times_live: [livetime(time_live, tz)]}
59 + refs -> %{refs | times: [time | refs.times], times_live: [livetime(time_live, tz) | refs.times_live]}
50 60 end)
51 61 end)
52 − |> Enum.map(fn {_k,v} -> v |> Map.put(:times, v.times |> Enum.reverse) end)
62 + |> Enum.map(fn {_k,v} ->
63 + case Enum.any?(v.times_live, fn x -> x != nil end) do
64 + true -> v |> Map.put(:times, v.times |> Enum.reverse) |> Map.put(:times_live, v.times_live |> Enum.reverse)
65 + false -> v |> Map.put(:times, v.times |> Enum.reverse) |> Map.delete(:times_live)
66 + end
67 + end)
53 68
54 69 :gbfs ->
55 70 data
modified apps/room_sanctum/lib/room_sanctum/storage.ex
+162 −76
@@ -807,6 +807,13 @@ defmodule RoomSanctum.Storage do
807 807 val
808 808 end
809 809
810 + defp conditional_where(queryable, source_id, stop_id, timestamp_time_hour, timestamp_time) do
811 + case timestamp_time < timestamp_time_hour do
812 + true -> where(queryable, [st], st.source_id == ^source_id and st.arrival_time >= ^timestamp_time and st.arrival_time <= ^timestamp_time_hour and st.stop_id == ^stringify(stop_id))
813 + false -> where(queryable, [st], st.source_id == ^source_id and (st.arrival_time >= ^timestamp_time and st.arrival_time <= ^Time.new!(23, 59, 50) or ^Time.new!(0,0,0) <= st.arrival_time and st.arrival_time <= ^timestamp_time_hour) and st.stop_id == ^stringify(stop_id))
814 + end
815 + end
816 +
810 817 def get_upcoming_arrivals_for_stop(source_id, stop_id, limit \\ 16, timestamp \\ :now) do
811 818 source = Cfg.get_source!(source_id)
812 819 tz = source.config.tz
@@ -823,82 +830,161 @@ defmodule RoomSanctum.Storage do
823 830
824 831 timestamp_time_hour = timestamp |> DateTime.add(60*60) |> DateTime.to_time()
825 832
826 − q =
827 − from st in StopTime,
828 − where:
829 − st.source_id == ^source_id and st.arrival_time >= ^timestamp_time and st.arrival_time <= ^timestamp_time_hour and st.stop_id == ^stringify(stop_id),
830 − order_by: [
831 − asc: st.arrival_time
832 − ],
833 − limit: ^limit,
834 − left_join: s in Stop,
835 − on: s.stop_id == st.stop_id,
836 − left_join: t in Trip,
837 − on: t.trip_id == st.trip_id,
838 − left_join: r in Route,
839 − on: t.route_id == r.route_id,
840 − left_join: d in Direction,
841 − on: t.direction_id == d.direction_id and t.route_id == d.route_id,
842 − left_join: c in Calendar,
843 − on: t.service_id == c.service_id,
844 − select: %{
845 − arrival_time: st.arrival_time,
846 − departure_time: st.departure_time,
847 − stop_id: st.stop_id,
848 − stop_sequence: st.stop_sequence,
849 − stop: %{
850 − stop_address: s.stop_address,
851 − stop_code: s.stop_code,
852 − stop_desc: s.stop_desc,
853 − stop_id: s.stop_id,
854 − stop_lat: s.stop_lat,
855 − stop_lon: s.stop_lon,
856 − stop_name: s.stop_name,
857 − stop_url: s.stop_url
858 − },
859 − trip_id: st.trip_id,
860 − trip: %{
861 − bikes_allowed: t.bikes_allowed,
862 − direction_id: t.direction_id,
863 − direction: %{
864 − direction: d.direction,
865 − direction_id: d.direction_id
866 − },
867 − route_id: t.route_id,
868 − route: %{
869 − line_id: r.line_id,
870 − route_color: r.route_color,
871 − route_desc: r.route_desc,
872 − route_fare_class: r.route_fare_class,
873 − route_id: r.route_id,
874 − route_long_name: r.route_long_name,
875 − route_short_name: r.route_short_name,
876 − route_sort_order: r.route_sort_order,
877 − route_text_color: r.route_text_color,
878 − route_type: r.route_type,
879 − route_url: r.route_url
880 − },
881 − service_id: t.service_id,
882 − service: %{
883 − service_id: c.service_id,
884 − service_name: c.service_name,
885 − start_date: c.start_date,
886 − end_date: c.end_date,
887 − monday: c.monday,
888 − tuesday: c.tuesday,
889 − wednesday: c.wednesday,
890 − thursday: c.thursday,
891 − friday: c.friday,
892 − saturday: c.saturday,
893 − sunday: c.sunday
894 − },
895 − trip_headsign: t.trip_headsign,
896 − trip_id: t.trip_id,
897 − trip_short_name: t.trip_short_name
898 − }
899 − }
900 −
901 − Repo.all(q)
833 + StopTime
834 + |> conditional_where(source_id, stop_id, timestamp_time_hour, timestamp_time)
835 + |> order_by([st], asc: st.arrival_time)
836 + |> limit(^limit)
837 + |> join(:left, [st], s in Stop, as: :stop, on: s.stop_id == st.stop_id)
838 + |> join(:left, [st], t in Trip, as: :trip, on: t.trip_id == st.trip_id)
839 + |> join(:left, [_st, trip: t], r in Route, as: :route, on: t.route_id == r.route_id)
840 + |> join(:left, [_st, trip: t], d in Direction, as: :direction, on: t.direction_id == d.direction_id and t.route_id == d.route_id )
841 + |> join(:left, [_st, trip: t], c in Calendar, as: :calendar, on: t.service_id == c.service_id)
842 + |> select(
843 + [st,s,t,r,d,c],
844 +# [c,d,r,t,s,st],
845 +# [st,:stop,:trip,:route,:direction,:calendar],
846 + %{
847 + arrival_time: st.arrival_time,
848 + arrival_time_live_ts: nil,
849 + arrival_time_live_delay: nil,
850 + arrival_time_live_uncertianty: nil,
851 + departure_time: st.departure_time,
852 + stop_id: st.stop_id,
853 + stop_sequence: st.stop_sequence,
854 + stop: %{
855 + stop_address: s.stop_address,
856 + stop_code: s.stop_code,
857 + stop_desc: s.stop_desc,
858 + stop_id: s.stop_id,
859 + stop_lat: s.stop_lat,
860 + stop_lon: s.stop_lon,
861 + stop_name: s.stop_name,
862 + stop_url: s.stop_url
863 + },
864 + trip_id: st.trip_id,
865 + trip: %{
866 + bikes_allowed: t.bikes_allowed,
867 + direction_id: t.direction_id,
868 + direction: %{
869 + direction: d.direction,
870 + direction_id: d.direction_id
871 + },
872 + route_id: t.route_id,
873 + route: %{
874 + line_id: r.line_id,
875 + route_color: r.route_color,
876 + route_desc: r.route_desc,
877 + route_fare_class: r.route_fare_class,
878 + route_id: r.route_id,
879 + route_long_name: r.route_long_name,
880 + route_short_name: r.route_short_name,
881 + route_sort_order: r.route_sort_order,
882 + route_text_color: r.route_text_color,
883 + route_type: r.route_type,
884 + route_url: r.route_url
885 + },
886 + service_id: t.service_id,
887 + service: %{
888 + service_id: c.service_id,
889 + service_name: c.service_name,
890 + start_date: c.start_date,
891 + end_date: c.end_date,
892 + monday: c.monday,
893 + tuesday: c.tuesday,
894 + wednesday: c.wednesday,
895 + thursday: c.thursday,
896 + friday: c.friday,
897 + saturday: c.saturday,
898 + sunday: c.sunday
899 + },
900 + trip_headsign: t.trip_headsign,
901 + trip_id: t.trip_id,
902 + trip_short_name: t.trip_short_name
903 + },
904 + tz: ^tz
905 + }
906 + )
907 + |> Repo.all
908 +
909 +# q =
910 +# from st in StopTime,
911 +# where: st.source_id == ^source_id and st.arrival_time >= ^timestamp_time and st.arrival_time <= ^timestamp_time_hour and st.stop_id == ^stringify(stop_id),
912 +# order_by: [
913 +# asc: st.arrival_time
914 +# ],
915 +# limit: ^limit,
916 +# left_join: s in Stop,
917 +# on: s.stop_id == st.stop_id,
918 +# left_join: t in Trip,
919 +# on: t.trip_id == st.trip_id,
920 +# left_join: r in Route,
921 +# on: t.route_id == r.route_id,
922 +# left_join: d in Direction,
923 +# on: t.direction_id == d.direction_id and t.route_id == d.route_id,
924 +# left_join: c in Calendar,
925 +# on: t.service_id == c.service_id,
926 +# select: %{
927 +# arrival_time: st.arrival_time,
928 +# arrival_time_live_ts: nil,
929 +# arrival_time_live_delay: nil,
930 +# arrival_time_live_uncertianty: nil,
931 +# departure_time: st.departure_time,
932 +# stop_id: st.stop_id,
933 +# stop_sequence: st.stop_sequence,
934 +# stop: %{
935 +# stop_address: s.stop_address,
936 +# stop_code: s.stop_code,
937 +# stop_desc: s.stop_desc,
938 +# stop_id: s.stop_id,
939 +# stop_lat: s.stop_lat,
940 +# stop_lon: s.stop_lon,
941 +# stop_name: s.stop_name,
942 +# stop_url: s.stop_url
943 +# },
944 +# trip_id: st.trip_id,
945 +# trip: %{
946 +# bikes_allowed: t.bikes_allowed,
947 +# direction_id: t.direction_id,
948 +# direction: %{
949 +# direction: d.direction,
950 +# direction_id: d.direction_id
951 +# },
952 +# route_id: t.route_id,
953 +# route: %{
954 +# line_id: r.line_id,
955 +# route_color: r.route_color,
956 +# route_desc: r.route_desc,
957 +# route_fare_class: r.route_fare_class,
958 +# route_id: r.route_id,
959 +# route_long_name: r.route_long_name,
960 +# route_short_name: r.route_short_name,
961 +# route_sort_order: r.route_sort_order,
962 +# route_text_color: r.route_text_color,
963 +# route_type: r.route_type,
964 +# route_url: r.route_url
965 +# },
966 +# service_id: t.service_id,
967 +# service: %{
968 +# service_id: c.service_id,
969 +# service_name: c.service_name,
970 +# start_date: c.start_date,
971 +# end_date: c.end_date,
972 +# monday: c.monday,
973 +# tuesday: c.tuesday,
974 +# wednesday: c.wednesday,
975 +# thursday: c.thursday,
976 +# friday: c.friday,
977 +# saturday: c.saturday,
978 +# sunday: c.sunday
979 +# },
980 +# trip_headsign: t.trip_headsign,
981 +# trip_id: t.trip_id,
982 +# trip_short_name: t.trip_short_name
983 +# },
984 +# tz: ^tz
985 +# }
986 +#
987 +# Repo.all(q)
902 988 end
903 989
904 990 alias RoomSanctum.Storage.GBFS.V1.SysInfo
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/form_component.html.heex
+29 −0
@@ -97,7 +97,36 @@
97 97 </span>
98 98 </label>
99 99 </div>
100 + <div class="form-control">
101 + <label>Realtime URL Service Alerts</label>
102 + <%= text_input gtfs_form, :url_rt_sa, class: "input w-full max-w-xs" %>
103 + <label class="label">
104 + <span class="label-text-alt text-error">
105 + <%= error_tag gtfs_form, :url_rt_sa %>
106 + </span>
107 + </label>
108 + </div>
109 + <div class="form-control">
110 + <label>Realtime URL Trip Updates</label>
111 + <%= text_input gtfs_form, :url_rt_tu, class: "input w-full max-w-xs" %>
112 + <label class="label">
113 + <span class="label-text-alt text-error">
114 + <%= error_tag gtfs_form, :url_rt_tu %>
115 + </span>
116 + </label>
117 + </div>
118 + <div class="form-control">
119 + <label>Realtime URL Vehicle Positions</label>
120 + <%= text_input gtfs_form, :url_rt_vp, class: "input w-full max-w-xs" %>
121 + <label class="label">
122 + <span class="label-text-alt text-error">
123 + <%= error_tag gtfs_form, :url_rt_vp %>
124 + </span>
125 + </label>
126 + </div>
127 +
100 128 <% end %>
129 +
101 130 <% :gbfs -> %>
102 131 <%= polymorphic_embed_inputs_for f, :config, :gbfs, fn gbfs_form -> %>
103 132 <div class="form-control">

Parents: b5c06fa