@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DiffusionRepositoryStorageManagementPanel
4 extends DiffusionRepositoryManagementPanel {
5
6 const PANELKEY = 'storage';
7
8 public function getManagementPanelLabel() {
9 return pht('Storage');
10 }
11
12 public function getManagementPanelOrder() {
13 return 600;
14 }
15
16 public function getManagementPanelIcon() {
17 $repository = $this->getRepository();
18
19 if ($repository->getAlmanacServicePHID()) {
20 return 'fa-sitemap';
21 } else if ($repository->isHosted()) {
22 return 'fa-database';
23 } else {
24 return 'fa-download';
25 }
26 }
27
28 public function buildManagementPanelCurtain() {
29 $repository = $this->getRepository();
30 $viewer = $this->getViewer();
31 $action_list = $this->newActionList();
32
33 $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
34
35 $action_list->addAction(
36 id(new PhabricatorActionView())
37 ->setIcon('fa-book')
38 ->setHref($doc_href)
39 ->setName(pht('Cluster Documentation')));
40
41 return $this->newCurtainView()
42 ->setActionList($action_list);
43 }
44
45 public function buildManagementPanelContent() {
46 return array(
47 $this->buildStorageStatusPanel(),
48 $this->buildClusterStatusPanel(),
49 $this->buildRefsStatusPanels(),
50 );
51 }
52
53 private function buildStorageStatusPanel() {
54 $repository = $this->getRepository();
55 $viewer = $this->getViewer();
56
57 $view = id(new PHUIPropertyListView())
58 ->setViewer($viewer);
59
60 if ($repository->usesLocalWorkingCopy()) {
61 $storage_path = $repository->getLocalPath();
62 } else {
63 $storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
64 }
65
66 $service_phid = $repository->getAlmanacServicePHID();
67 if ($service_phid) {
68 $storage_service = $viewer->renderHandle($service_phid);
69 } else {
70 $storage_service = phutil_tag('em', array(), pht('Local'));
71 }
72
73 $view->addProperty(pht('Storage Path'), $storage_path);
74 $view->addProperty(pht('Storage Cluster'), $storage_service);
75
76 return $this->newBox(pht('Storage'), $view);
77 }
78
79 private function buildClusterStatusPanel() {
80 $repository = $this->getRepository();
81 $viewer = $this->getViewer();
82
83 $service_phid = $repository->getAlmanacServicePHID();
84 if ($service_phid) {
85 $service = id(new AlmanacServiceQuery())
86 ->setViewer($viewer)
87 ->withServiceTypes(
88 array(
89 AlmanacClusterRepositoryServiceType::SERVICETYPE,
90 ))
91 ->withPHIDs(array($service_phid))
92 ->needActiveBindings(true)
93 ->executeOne();
94 if (!$service) {
95 // TODO: Viewer may not have permission to see the service, or it may
96 // be invalid? Raise some more useful error here?
97 throw new Exception(pht('Unable to load cluster service.'));
98 }
99 } else {
100 $service = null;
101 }
102
103 Javelin::initBehavior('phabricator-tooltips');
104
105 $rows = array();
106 if ($service) {
107 $bindings = $service->getActiveBindings();
108 $bindings = mgroup($bindings, 'getDevicePHID');
109
110 // This is an unusual read which always comes from the master.
111 if (PhabricatorEnv::isReadOnly()) {
112 $versions = array();
113 } else {
114 $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
115 $repository->getPHID());
116 }
117
118 $versions = mpull($versions, null, 'getDevicePHID');
119
120 $sort = array();
121 foreach ($bindings as $key => $binding_group) {
122 $sort[$key] = id(new PhutilSortVector())
123 ->addString(head($binding_group)->getDevice()->getName());
124 }
125 $sort = msortv($sort, 'getSelf');
126 $bindings = array_select_keys($bindings, array_keys($sort)) + $bindings;
127
128 foreach ($bindings as $binding_group) {
129 $any_binding = head($binding_group);
130
131 $binding_icon = 'fa-folder-open green';
132 $binding_tip = pht('Active');
133
134 $binding_icon = id(new PHUIIconView())
135 ->setIcon($binding_icon)
136 ->addSigil('has-tooltip')
137 ->setMetadata(
138 array(
139 'tip' => $binding_tip,
140 ));
141
142 $device = $any_binding->getDevice();
143
144 $version = idx($versions, $device->getPHID());
145 if ($version) {
146 $version_number = $version->getRepositoryVersion();
147
148 $href = null;
149 if ($repository->isHosted()) {
150 $href = "/diffusion/pushlog/view/{$version_number}/";
151 } else {
152 $commit = id(new DiffusionCommitQuery())
153 ->setViewer($viewer)
154 ->withIDs(array($version_number))
155 ->executeOne();
156 if ($commit) {
157 $href = $commit->getURI();
158 }
159 }
160
161 if ($href) {
162 $version_number = phutil_tag(
163 'a',
164 array(
165 'href' => $href,
166 ),
167 $version_number);
168 }
169 } else {
170 $version_number = '-';
171 }
172
173 if ($version && $version->getIsWriting()) {
174 $is_writing = id(new PHUIIconView())
175 ->setIcon('fa-pencil green');
176 } else {
177 $is_writing = id(new PHUIIconView())
178 ->setIcon('fa-pencil grey');
179 }
180
181 $write_properties = null;
182 if ($version) {
183 $write_properties = $version->getWriteProperties();
184 if ($write_properties) {
185 try {
186 $write_properties = phutil_json_decode($write_properties);
187 } catch (Exception $ex) {
188 $write_properties = null;
189 }
190 }
191 }
192
193 $last_writer = null;
194 $writer_epoch = null;
195 if ($write_properties) {
196 $writer_phid = idx($write_properties, 'userPHID');
197
198 if ($writer_phid) {
199 $last_writer = $viewer->renderHandle($writer_phid);
200 }
201
202 $writer_epoch = idx($write_properties, 'epoch');
203 if ($writer_epoch) {
204 $writer_epoch = phabricator_datetime($writer_epoch, $viewer);
205 }
206 }
207
208 $rows[] = array(
209 $binding_icon,
210 phutil_tag(
211 'a',
212 array(
213 'href' => $device->getURI(),
214 ),
215 $device->getName()),
216 $version_number,
217 $is_writing,
218 $last_writer,
219 $writer_epoch,
220 );
221 }
222 }
223
224 $table = id(new AphrontTableView($rows))
225 ->setNoDataString(pht('This is not a cluster repository.'))
226 ->setHeaders(
227 array(
228 null,
229 pht('Device'),
230 pht('Version'),
231 pht('Writing'),
232 pht('Last Writer'),
233 pht('Last Write At'),
234 ))
235 ->setColumnClasses(
236 array(
237 null,
238 null,
239 null,
240 'right wide',
241 null,
242 'date',
243 ));
244
245 return $this->newBox(pht('Cluster Status'), $table);
246 }
247
248 private function buildRefsStatusPanels() {
249 $repository = $this->getRepository();
250
251 $service_phid = $repository->getAlmanacServicePHID();
252 if (!$service_phid) {
253 // If this repository isn't clustered, don't bother rendering anything.
254 // There are enough other context clues that another empty panel isn't
255 // useful.
256 return;
257 }
258
259 $all_protocols = array(
260 'http',
261 'https',
262 'ssh',
263 );
264
265 $readable_panel = $this->buildRefsStatusPanel(
266 pht('Readable Service Refs'),
267 array(
268 'neverProxy' => false,
269 'protocols' => $all_protocols,
270 'writable' => false,
271 ));
272
273 $writable_panel = $this->buildRefsStatusPanel(
274 pht('Writable Service Refs'),
275 array(
276 'neverProxy' => false,
277 'protocols' => $all_protocols,
278 'writable' => true,
279 ));
280
281 return array(
282 $readable_panel,
283 $writable_panel,
284 );
285 }
286
287 private function buildRefsStatusPanel(
288 $title,
289 $options) {
290
291 $repository = $this->getRepository();
292 $viewer = $this->getViewer();
293
294 $caught = null;
295 $refs = array();
296
297 try {
298 $refs = $repository->getAlmanacServiceRefs($viewer, $options);
299 } catch (Exception $ex) {
300 $caught = $ex;
301 } catch (Throwable $ex) {
302 $caught = $ex;
303 }
304
305 $info_view = null;
306 if ($caught) {
307 $info_view = id(new PHUIInfoView())
308 ->setErrors(
309 array(
310 phutil_escape_html_newlines($caught->getMessage()),
311 ));
312 }
313
314 $phids = array();
315 foreach ($refs as $ref) {
316 $phids[] = $ref->getDevicePHID();
317 }
318
319 $handles = $viewer->loadHandles($phids);
320
321 $icon_writable = id(new PHUIIconView())
322 ->setIcon('fa-pencil', 'green');
323
324 $icon_unwritable = id(new PHUIIconView())
325 ->setIcon('fa-times', 'grey');
326
327 $rows = array();
328 foreach ($refs as $ref) {
329 $device_phid = $ref->getDevicePHID();
330 $device_handle = $handles[$device_phid];
331
332 if ($ref->isWritable()) {
333 $writable_icon = $icon_writable;
334 $writable_text = pht('Read/Write');
335 } else {
336 $writable_icon = $icon_unwritable;
337 $writable_text = pht('Read Only');
338 }
339
340 $rows[] = array(
341 $device_handle->renderLink(),
342 $ref->getURI(),
343 $writable_icon,
344 $writable_text,
345 );
346 }
347
348 $table = id(new AphrontTableView($rows))
349 ->setNoDataString(pht('No repository service refs available.'))
350 ->setHeaders(
351 array(
352 pht('Device'),
353 pht('Internal Service URI'),
354 null,
355 pht('I/O'),
356 ))
357 ->setColumnClasses(
358 array(
359 null,
360 'wide',
361 'icon',
362 null,
363 ));
364
365 $box_view = $this->newBox($title, $table);
366
367 if ($info_view) {
368 $box_view->setInfoView($info_view);
369 }
370
371 return $box_view;
372 }
373
374}