1
|
|
|
""" |
2
|
|
|
byceps.blueprints.admin.guest_server.views |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2022 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from __future__ import annotations |
10
|
1 |
|
import ipaddress |
11
|
1 |
|
from typing import Iterable, Optional |
12
|
|
|
|
13
|
1 |
|
from flask import abort, g, request, url_for |
14
|
1 |
|
from flask_babel import gettext |
15
|
|
|
|
16
|
1 |
|
from ....services.guest_server import service as guest_server_service |
17
|
1 |
|
from ....services.guest_server.transfer.models import ( |
18
|
|
|
Address, |
19
|
|
|
IPAddress, |
20
|
|
|
Setting, |
21
|
|
|
) |
22
|
1 |
|
from ....services.party import service as party_service |
23
|
1 |
|
from ....services.user import service as user_service |
24
|
1 |
|
from ....signals import guest_server as guest_server_signals |
25
|
1 |
|
from ....util.export import serialize_tuples_to_csv |
26
|
1 |
|
from ....util.framework.blueprint import create_blueprint |
27
|
1 |
|
from ....util.framework.flash import flash_success |
28
|
1 |
|
from ....util.framework.templating import templated |
29
|
1 |
|
from ....util.views import ( |
30
|
|
|
permission_required, |
31
|
|
|
redirect_to, |
32
|
|
|
respond_no_content_with_location, |
33
|
|
|
textified, |
34
|
|
|
) |
35
|
|
|
|
36
|
1 |
|
from .forms import ( |
37
|
|
|
AddressCreateForm, |
38
|
|
|
AddressUpdateForm, |
39
|
|
|
ServerCreateForm, |
40
|
|
|
ServerUpdateForm, |
41
|
|
|
SettingUpdateForm, |
42
|
|
|
) |
43
|
|
|
|
44
|
|
|
|
45
|
1 |
|
blueprint = create_blueprint('guest_server_admin', __name__) |
46
|
|
|
|
47
|
|
|
|
48
|
|
|
# -------------------------------------------------------------------- # |
49
|
|
|
# servers |
50
|
|
|
|
51
|
|
|
|
52
|
1 |
View Code Duplication |
@blueprint.get('/for_party/<party_id>/servers') |
53
|
1 |
|
@permission_required('guest_server.view') |
54
|
1 |
|
@templated |
55
|
|
|
def server_index(party_id): |
56
|
|
|
"""Show guest servers for a party.""" |
57
|
|
|
party = _get_party_or_404(party_id) |
58
|
|
|
|
59
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
60
|
|
|
|
61
|
|
|
servers = guest_server_service.get_all_servers_for_party(party.id) |
62
|
|
|
|
63
|
|
|
creator_ids = {server.creator_id for server in servers} |
64
|
|
|
owner_ids = {server.owner_id for server in servers} |
65
|
|
|
user_ids = creator_ids.union(owner_ids) |
66
|
|
|
users = user_service.get_users(user_ids, include_avatars=True) |
67
|
|
|
users_by_id = user_service.index_users_by_id(users) |
68
|
|
|
|
69
|
|
|
return { |
70
|
|
|
'party': party, |
71
|
|
|
'setting': setting, |
72
|
|
|
'servers': servers, |
73
|
|
|
'users_by_id': users_by_id, |
74
|
|
|
'sort_addresses': _sort_addresses, |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
|
78
|
1 |
|
@blueprint.get('/servers/<server_id>') |
79
|
1 |
|
@permission_required('guest_server.view') |
80
|
1 |
|
@templated |
81
|
|
|
def server_view(server_id): |
82
|
|
|
"""Show guest server.""" |
83
|
|
|
server = _get_server_or_404(server_id) |
84
|
|
|
party = party_service.get_party(server.party_id) |
85
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
86
|
|
|
|
87
|
|
|
user_ids = {server.creator_id, server.owner_id} |
88
|
|
|
users = user_service.get_users_for_admin(user_ids) |
89
|
|
|
users_by_id = user_service.index_users_by_id(users) |
90
|
|
|
|
91
|
|
|
return { |
92
|
|
|
'party': party, |
93
|
|
|
'server': server, |
94
|
|
|
'setting': setting, |
95
|
|
|
'users_by_id': users_by_id, |
96
|
|
|
'sort_addresses': _sort_addresses, |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
|
100
|
1 |
|
@blueprint.get('/for_party/<party_id>/servers/create') |
101
|
1 |
|
@permission_required('guest_server.administrate') |
102
|
1 |
|
@templated |
103
|
1 |
|
def server_create_form(party_id, erroneous_form=None): |
104
|
|
|
"""Show a form to create a server.""" |
105
|
|
|
party = _get_party_or_404(party_id) |
106
|
|
|
|
107
|
|
|
form = erroneous_form if erroneous_form else ServerCreateForm() |
108
|
|
|
|
109
|
|
|
return { |
110
|
|
|
'party': party, |
111
|
|
|
'form': form, |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
|
115
|
1 |
|
@blueprint.post('/for_party/<party_id>/servers') |
116
|
1 |
|
@permission_required('guest_server.administrate') |
117
|
|
|
def server_create(party_id): |
118
|
|
|
"""Create a server.""" |
119
|
|
|
party = _get_party_or_404(party_id) |
120
|
|
|
|
121
|
|
|
form = ServerCreateForm(request.form) |
122
|
|
|
if not form.validate(): |
123
|
|
|
return server_create_form(party_id, form) |
124
|
|
|
|
125
|
|
|
creator = g.user |
126
|
|
|
owner = form.owner.data |
127
|
|
|
notes_admin = form.notes_admin.data.strip() |
128
|
|
|
approved = form.approved.data |
129
|
|
|
ip_address = _to_ip_address(form.ip_address.data.strip()) |
130
|
|
|
hostname = form.hostname.data.strip() or None |
131
|
|
|
netmask = _to_ip_address(form.netmask.data.strip()) |
132
|
|
|
gateway = _to_ip_address(form.gateway.data.strip()) |
133
|
|
|
|
134
|
|
|
server, event = guest_server_service.create_server( |
135
|
|
|
party.id, |
136
|
|
|
creator.id, |
137
|
|
|
owner.id, |
138
|
|
|
notes_admin=notes_admin, |
139
|
|
|
approved=approved, |
140
|
|
|
ip_address=ip_address, |
141
|
|
|
hostname=hostname, |
142
|
|
|
netmask=netmask, |
143
|
|
|
gateway=gateway, |
144
|
|
|
) |
145
|
|
|
|
146
|
|
|
flash_success(gettext('The server has been registered.')) |
147
|
|
|
|
148
|
|
|
guest_server_signals.guest_server_registered.send(None, event=event) |
149
|
|
|
|
150
|
|
|
return redirect_to('.server_view', server_id=server.id) |
151
|
|
|
|
152
|
|
|
|
153
|
1 |
|
@blueprint.get('/servers/<uuid:server_id>/update') |
154
|
1 |
|
@permission_required('guest_server.administrate') |
155
|
1 |
|
@templated |
156
|
1 |
|
def server_update_form(server_id, erroneous_form=None): |
157
|
|
|
"""Show form to update a server.""" |
158
|
|
|
server = _get_server_or_404(server_id) |
159
|
|
|
party = party_service.get_party(server.party_id) |
160
|
|
|
|
161
|
|
|
form = erroneous_form if erroneous_form else ServerUpdateForm(obj=server) |
162
|
|
|
|
163
|
|
|
return { |
164
|
|
|
'party': party, |
165
|
|
|
'server': server, |
166
|
|
|
'form': form, |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
|
170
|
1 |
|
@blueprint.post('/servers/<uuid:server_id>') |
171
|
1 |
|
@permission_required('guest_server.administrate') |
172
|
|
|
def server_update(server_id): |
173
|
|
|
"""Update a server.""" |
174
|
|
|
server = _get_server_or_404(server_id) |
175
|
|
|
|
176
|
|
|
form = ServerUpdateForm(request.form) |
177
|
|
|
if not form.validate(): |
178
|
|
|
return server_update_form(server.id, form) |
179
|
|
|
|
180
|
|
|
notes_admin = form.notes_admin.data.strip() or None |
181
|
|
|
approved = form.approved.data |
182
|
|
|
|
183
|
|
|
guest_server_service.update_server(server.id, notes_admin, approved) |
184
|
|
|
|
185
|
|
|
flash_success(gettext('Changes have been saved.')) |
186
|
|
|
|
187
|
|
|
return redirect_to('.server_view', server_id=server.id) |
188
|
|
|
|
189
|
|
|
|
190
|
1 |
|
@blueprint.delete('/guest_servers/<uuid:server_id>') |
191
|
1 |
|
@permission_required('guest_server.administrate') |
192
|
1 |
|
@respond_no_content_with_location |
193
|
|
|
def server_delete(server_id): |
194
|
|
|
"""Delete a guest server.""" |
195
|
|
|
server = _get_server_or_404(server_id) |
196
|
|
|
|
197
|
|
|
party_id = server.party_id |
198
|
|
|
|
199
|
|
|
guest_server_service.delete_server(server_id) |
200
|
|
|
|
201
|
|
|
flash_success(gettext('Server has been deleted.')) |
202
|
|
|
|
203
|
|
|
return url_for('.server_index', party_id=party_id) |
204
|
|
|
|
205
|
|
|
|
206
|
|
|
# -------------------------------------------------------------------- # |
207
|
|
|
# addresses |
208
|
|
|
|
209
|
|
|
|
210
|
1 |
View Code Duplication |
@blueprint.get('/for_party/<party_id>/addresses') |
211
|
1 |
|
@permission_required('guest_server.view') |
212
|
1 |
|
@templated |
213
|
|
|
def address_index(party_id): |
214
|
|
|
"""Show addresses for a party.""" |
215
|
|
|
party = _get_party_or_404(party_id) |
216
|
|
|
|
217
|
|
|
servers = guest_server_service.get_all_servers_for_party(party.id) |
218
|
|
|
|
219
|
|
|
addresses = [] |
220
|
|
|
for server in servers: |
221
|
|
|
addresses.extend(server.addresses) |
222
|
|
|
|
223
|
|
|
user_ids = {server.owner_id for server in servers} |
224
|
|
|
users = user_service.get_users(user_ids, include_avatars=True) |
225
|
|
|
users_by_id = user_service.index_users_by_id(users) |
226
|
|
|
owners_by_server_id = { |
227
|
|
|
server.id: users_by_id[server.owner_id] for server in servers |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
231
|
|
|
|
232
|
|
|
return { |
233
|
|
|
'party': party, |
234
|
|
|
'addresses': addresses, |
235
|
|
|
'sort_addresses': _sort_addresses, |
236
|
|
|
'owners_by_server_id': owners_by_server_id, |
237
|
|
|
'setting': setting, |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
|
241
|
1 |
|
@blueprint.get('/for_party/<party_id>/addresses/export/netbox') |
242
|
1 |
|
@permission_required('guest_server.view') |
243
|
1 |
|
@textified |
244
|
|
|
def address_export_netbox(party_id): |
245
|
|
|
"""Export addresses for a party as NetBox-compatible CSV. |
246
|
|
|
|
247
|
|
|
Suitable for importing into NetBox |
248
|
|
|
(https://github.com/netbox-community/netbox). |
249
|
|
|
""" |
250
|
|
|
party = _get_party_or_404(party_id) |
251
|
|
|
|
252
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
253
|
|
|
|
254
|
|
|
all_servers = guest_server_service.get_all_servers_for_party(party.id) |
255
|
|
|
approved_servers = [server for server in all_servers if server.approved] |
256
|
|
|
|
257
|
|
|
owner_ids = {server.owner_id for server in approved_servers} |
258
|
|
|
owners = user_service.get_users(owner_ids, include_avatars=False) |
259
|
|
|
owners_by_id = user_service.index_users_by_id(owners) |
260
|
|
|
|
261
|
|
|
# field names as defined by NetBox |
262
|
|
|
field_names = ('address', 'status', 'dns_name', 'description') |
263
|
|
|
|
264
|
|
|
rows = [] |
265
|
|
|
|
266
|
|
|
for server in approved_servers: |
267
|
|
|
for address in server.addresses: |
268
|
|
|
if address.ip_address and address.hostname: |
269
|
|
|
rows.append( |
270
|
|
|
( |
271
|
|
|
f'{str(address.ip_address)}/24', |
272
|
|
|
'active', |
273
|
|
|
_get_full_hostname(address.hostname, setting), |
274
|
|
|
owners_by_id[server.owner_id].screen_name or 'unknown', |
275
|
|
|
) |
276
|
|
|
) |
277
|
|
|
|
278
|
|
|
rows.sort() |
279
|
|
|
|
280
|
|
|
return serialize_tuples_to_csv([field_names] + rows) |
281
|
|
|
|
282
|
|
|
|
283
|
1 |
|
def _get_full_hostname(hostname: str, setting: Setting) -> str: |
284
|
|
|
"""Return the hostname with the domain (if configured) appended.""" |
285
|
|
|
return hostname + '.' + setting.domain if setting.domain else hostname |
286
|
|
|
|
287
|
|
|
|
288
|
1 |
|
@blueprint.get('/servers/<uuid:server_id>/addresses/create') |
289
|
1 |
|
@permission_required('guest_server.administrate') |
290
|
1 |
|
@templated |
291
|
1 |
|
def address_create_form(server_id, erroneous_form=None): |
292
|
|
|
"""Show a form to add an address to a server.""" |
293
|
|
|
server = _get_server_or_404(server_id) |
294
|
|
|
party = party_service.get_party(server.party_id) |
295
|
|
|
|
296
|
|
|
form = erroneous_form if erroneous_form else AddressCreateForm() |
297
|
|
|
|
298
|
|
|
return { |
299
|
|
|
'party': party, |
300
|
|
|
'server': server, |
301
|
|
|
'form': form, |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
|
305
|
1 |
|
@blueprint.post('/servers/<uuid:server_id>/addresses') |
306
|
1 |
|
@permission_required('guest_server.administrate') |
307
|
|
|
def address_create(server_id): |
308
|
|
|
"""Add an address to a server.""" |
309
|
|
|
server = _get_server_or_404(server_id) |
310
|
|
|
|
311
|
|
|
form = AddressCreateForm(request.form) |
312
|
|
|
if not form.validate(): |
313
|
|
|
return address_create_form(server_id, form) |
314
|
|
|
|
315
|
|
|
ip_address = _to_ip_address(form.ip_address.data.strip()) |
316
|
|
|
hostname = form.hostname.data.strip() or None |
317
|
|
|
netmask = _to_ip_address(form.netmask.data.strip()) |
318
|
|
|
gateway = _to_ip_address(form.gateway.data.strip()) |
319
|
|
|
|
320
|
|
|
address = guest_server_service.create_address( |
321
|
|
|
server.id, ip_address, hostname, netmask, gateway |
322
|
|
|
) |
323
|
|
|
|
324
|
|
|
flash_success(gettext('The address has been added.')) |
325
|
|
|
|
326
|
|
|
return redirect_to('.server_view', server_id=server.id) |
327
|
|
|
|
328
|
|
|
|
329
|
1 |
|
@blueprint.get('/addresses/<uuid:address_id>/update') |
330
|
1 |
|
@permission_required('guest_server.administrate') |
331
|
1 |
|
@templated |
332
|
1 |
|
def address_update_form(address_id, erroneous_form=None): |
333
|
|
|
"""Show form to update an address.""" |
334
|
|
|
address = _get_address_or_404(address_id) |
335
|
|
|
server = guest_server_service.find_server(address.server_id) |
336
|
|
|
party = party_service.get_party(server.party_id) |
337
|
|
|
|
338
|
|
|
form = erroneous_form if erroneous_form else AddressUpdateForm(obj=address) |
339
|
|
|
|
340
|
|
|
return { |
341
|
|
|
'party': party, |
342
|
|
|
'address': address, |
343
|
|
|
'form': form, |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
|
347
|
1 |
|
@blueprint.post('/addresses/<uuid:address_id>') |
348
|
1 |
|
@permission_required('guest_server.administrate') |
349
|
|
|
def address_update(address_id): |
350
|
|
|
"""Update an address.""" |
351
|
|
|
address = _get_address_or_404(address_id) |
352
|
|
|
server = guest_server_service.find_server(address.server_id) |
353
|
|
|
|
354
|
|
|
form = AddressUpdateForm(request.form) |
355
|
|
|
if not form.validate(): |
356
|
|
|
return address_update_form(address.id, form) |
357
|
|
|
|
358
|
|
|
ip_address = _to_ip_address(form.ip_address.data.strip()) |
359
|
|
|
hostname = form.hostname.data.strip() or None |
360
|
|
|
netmask = _to_ip_address(form.netmask.data.strip()) |
361
|
|
|
gateway = _to_ip_address(form.gateway.data.strip()) |
362
|
|
|
|
363
|
|
|
guest_server_service.update_address( |
364
|
|
|
address.id, ip_address, hostname, netmask, gateway |
365
|
|
|
) |
366
|
|
|
|
367
|
|
|
flash_success(gettext('Changes have been saved.')) |
368
|
|
|
|
369
|
|
|
return redirect_to('.server_view', server_id=server.id) |
370
|
|
|
|
371
|
|
|
|
372
|
|
|
# -------------------------------------------------------------------- # |
373
|
|
|
# setting |
374
|
|
|
|
375
|
|
|
|
376
|
1 |
|
@blueprint.get('/for_party/<party_id>/settings') |
377
|
1 |
|
@permission_required('guest_server.view') |
378
|
1 |
|
@templated |
379
|
|
|
def setting_view(party_id): |
380
|
|
|
"""Show settings for a party.""" |
381
|
|
|
party = _get_party_or_404(party_id) |
382
|
|
|
|
383
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
384
|
|
|
|
385
|
|
|
return { |
386
|
|
|
'party': party, |
387
|
|
|
'setting': setting, |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
|
391
|
1 |
|
@blueprint.get('/for_party/<party_id>/settings/update') |
392
|
1 |
|
@permission_required('guest_server.administrate') |
393
|
1 |
|
@templated |
394
|
1 |
|
def setting_update_form(party_id, erroneous_form=None): |
395
|
|
|
"""Show form to update the settings for a party.""" |
396
|
|
|
party = _get_party_or_404(party_id) |
397
|
|
|
|
398
|
|
|
setting = guest_server_service.get_setting_for_party(party.id) |
399
|
|
|
|
400
|
|
|
form = erroneous_form if erroneous_form else SettingUpdateForm(obj=setting) |
401
|
|
|
|
402
|
|
|
return { |
403
|
|
|
'party': party, |
404
|
|
|
'form': form, |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
|
408
|
1 |
|
@blueprint.post('/for_party/<party_id>/settings') |
409
|
1 |
|
@permission_required('guest_server.administrate') |
410
|
|
|
def setting_update(party_id): |
411
|
|
|
"""Update the settings for a party.""" |
412
|
|
|
party = _get_party_or_404(party_id) |
413
|
|
|
|
414
|
|
|
form = SettingUpdateForm(request.form) |
415
|
|
|
if not form.validate(): |
416
|
|
|
return setting_update_form(party.id, form) |
417
|
|
|
|
418
|
|
|
netmask = _to_ip_address(form.netmask.data.strip()) |
419
|
|
|
gateway = _to_ip_address(form.gateway.data.strip()) |
420
|
|
|
dns_server1 = _to_ip_address(form.dns_server1.data.strip()) |
421
|
|
|
dns_server2 = _to_ip_address(form.dns_server2.data.strip()) |
422
|
|
|
domain = form.domain.data.strip() or None |
423
|
|
|
|
424
|
|
|
guest_server_service.update_setting( |
425
|
|
|
party.id, netmask, gateway, dns_server1, dns_server2, domain |
426
|
|
|
) |
427
|
|
|
|
428
|
|
|
flash_success(gettext('Changes have been saved.')) |
429
|
|
|
|
430
|
|
|
return redirect_to('.setting_view', party_id=party.id) |
431
|
|
|
|
432
|
|
|
|
433
|
|
|
# -------------------------------------------------------------------- # |
434
|
|
|
# helpers |
435
|
|
|
|
436
|
|
|
|
437
|
1 |
|
def _get_party_or_404(party_id): |
438
|
|
|
party = party_service.find_party(party_id) |
439
|
|
|
|
440
|
|
|
if party is None: |
441
|
|
|
abort(404) |
442
|
|
|
|
443
|
|
|
return party |
444
|
|
|
|
445
|
|
|
|
446
|
1 |
|
def _get_server_or_404(server_id): |
447
|
|
|
server = guest_server_service.find_server(server_id) |
448
|
|
|
|
449
|
|
|
if server is None: |
450
|
|
|
abort(404) |
451
|
|
|
|
452
|
|
|
return server |
453
|
|
|
|
454
|
|
|
|
455
|
1 |
|
def _get_address_or_404(address_id): |
456
|
|
|
address = guest_server_service.find_address(address_id) |
457
|
|
|
|
458
|
|
|
if address is None: |
459
|
|
|
abort(404) |
460
|
|
|
|
461
|
|
|
return address |
462
|
|
|
|
463
|
|
|
|
464
|
1 |
|
def _to_ip_address(value: str) -> Optional[IPAddress]: |
465
|
|
|
return ipaddress.ip_address(value) if value else None |
466
|
|
|
|
467
|
|
|
|
468
|
1 |
|
def _sort_addresses(addresses: Iterable[Address]) -> list[Address]: |
469
|
|
|
"""Sort addresses. |
470
|
|
|
|
471
|
|
|
By IP address first, hostname second. `None` at the end. |
472
|
|
|
""" |
473
|
|
|
return list( |
474
|
|
|
sorted( |
475
|
|
|
addresses, |
476
|
|
|
key=lambda addr: ( |
477
|
|
|
addr.ip_address is None, |
478
|
|
|
addr.ip_address, |
479
|
|
|
addr.hostname is None, |
480
|
|
|
addr.hostname, |
481
|
|
|
), |
482
|
|
|
) |
483
|
|
|
) |
484
|
|
|
|