Commit 9e2c3f28 authored by Robert-K's avatar Robert-K
Browse files

UPD: Pulled for swagger and fixed merge conflicts

parents d8f5954d 446e5de2
......@@ -30,7 +30,7 @@ frontend-build:
- python3 ./generate_base_model.py
- npm run build
artifacts:
expire_in: 1 days
expire_in: 7 days
paths:
- frontend/dist/
- frontend/src/api-services.gen/
......
import json
import requests
import os
AUTH = os.getenv('NETDB_AUTH')
API_BASE_URL = "https://www-net-devel.scc.kit.edu/api/3.0"
API_SCHEME = 'https'
API_HOST = 'www-net-devel.scc.kit.edu'
API_VERSION = '3.0'
API_BASE_URL = f'/api/{API_VERSION}'
BASE_DIR = 'src/api-services.gen/'
SWAGGER_TARGET_FILE = 'public/api.json'
swagger = {'swagger': '2.0',
'info': {
'description': """
Die Schnittstelle WebAPI bietet IT-Betreuern am KIT die Möglichkeit, eigene, speziell zugeschnittene Anwendungsprogramme für die Pflege ihrer netzwerk-spezifischen Anwendungsdaten (wie z.B. DNS) aufzubauen.
Die Schnittstelle setzt auf der Netzdatenbank des SCC (NetDB) auf und ist für eine vollautomatisierte Nutzung auf Basis des heutzutage üblichen textbasierten Datenaustauschformates JSON (JavaScript Object Notation) vorgesehen (s.a. http://de.wikipedia.org/wiki/JavaScript_Object_Notation).
Sie ist über HTTPS erreichbar und kann dadurch mit jeder beliebigen HTTP-Programmbibliothek benutzt werden. Im Rahmen der implementierten Systeme (Anwendungsbereiche), Objekttypen und Funktionen lassen sich sämtliche Daten durch entsprechende Dienstanforderungen (Requests) sowohl ausgeben bzw. abfragen als auch manipulieren (im Standardfall Eintragen, Ändern, Löschen). Die konkreten, jeweils versionsspezifischen Informationen und Parameter zu diesen Systemen, Objekttypen und Funktionen sind nicht Bestandteil dieser Dokumentation, sondern über selbstdokumentierende Indexabfragen der WebAPI erreichbar.""",
'version': API_VERSION,
'title': 'SCC NETDB-API'},
'externalDocs': {
'description': 'Weitere Dokumentation',
'url': f'https://www-net-devel-doku.scc.kit.edu/webapi/{API_VERSION}/intro'
},
'host': '',
'basePath': API_BASE_URL,
# 'schemes': [API_SCHEME],
'tags': [],
'paths': []
}
keyword_replacements = {'class': 'cls'}
files = {}
request = requests.get("{base}/wapi/function/list".format(base=API_BASE_URL), headers={'Authorization': AUTH})
sess = requests.Session()
sess.headers.update({'Authorization': AUTH})
request = sess.get("{scheme}://{base_host}{base_url}/wapi/function/list".format(scheme=API_SCHEME, base_host=API_HOST,
base_url=API_BASE_URL))
if not request.status_code == 200:
print("Fail! Unexpected status_code", request.status_code)
print("Is the NETDB_AUTH environment variable correct?")
......@@ -38,7 +68,7 @@ export default {
old_default = None
new_default = None
p_esc = p
for (unesc,esc) in keyword_replacements.items():
for (unesc, esc) in keyword_replacements.items():
p_esc = p_esc.replace(unesc, esc)
if 'old' in v:
old_default = v['old']['dataDefault']
......@@ -56,7 +86,7 @@ export default {
params_dict_old += '\'{p}\': {p_esc}_old, '.format(p=p, p_esc=p_esc)
params_str += '{p_esc}_old={d}, '.format(p_esc=p_esc, p=p, d=old_default)
if 'new' in v:
params_str += '{p}_new={d}, '.format(p=p,d=new_default)
params_str += '{p}_new={d}, '.format(p=p, d=new_default)
params_dict_new += '\'{p}\': {p}_new, '.format(p=p)
params_dict_new += '}'
......@@ -94,10 +124,134 @@ export default {
return Axios.get(`${{NETVSConfig.NETDB_API_BASE_URL}}/{fq_name_slash}`, cnf)
}},
"""
f_handle.write(func_str.format(params_dict_old=params_dict_old, params_dict=params_dict, name=f['name'], params=params_str, fq_name_slash=f['fq_name'].replace('.', '/')))
f_handle.write(
func_str.format(params_dict_old=params_dict_old, params_dict=params_dict, name=f['name'], params=params_str,
fq_name_slash=f['fq_name'].replace('.', '/')))
for f in files.values():
f.write("""}
""")
f.close()
def generateParamters(f):
if not f['is_data_manipulating']:
return [
{
'name': p, 'description': d['description'],
'required': d['old']['isRequired'] if 'old' in d else d['new']['isRequired'],
'schema': {
'type': d['dataType']['jsonName'],
'default': d['old']['dataDefault'],
'nullable': d['old']['isNullable'],
},
'in': 'query'
}
for p, d in f['parameters'].items()]
new_params = {
p: {
'type': d['dataType']['jsonName'],
'description': d['description'],
'required': d['new']['isRequired'],
'nullable': d['new']['isNullable'],
'default': d['new']['dataDefault']
}
for p, d in f['parameters'].items() if 'new' in d
}
old_params = {
p: {
'type': d['dataType']['jsonName'],
'description': d['description'],
'required': d['old']['isRequired'],
'nullable': d['old']['isNullable'],
'default': d['old']['dataDefault']
}
for p, d in f['parameters'].items() if 'old' in d
}
props = {}
if len(old_params) > 0:
props['old'] = {
'description': 'Alte Attribute zur einduetigen Identifizierung des Objekts',
'type': 'object',
'properties': old_params,
}
if len(new_params) > 0:
props['new'] = {
'description': 'Neue Angaben',
'type': 'object',
'properties': new_params,
}
return [{
'name': 'body',
'in': 'body',
'required': True,
'schema': {
'type': 'object',
'properties': props
}
}]
systems = sess.get(
"{scheme}://{base_host}{base_url}/".format(scheme=API_SCHEME, base_host=API_HOST, base_url=API_BASE_URL))
objects = sess.get(
"{scheme}://{base_host}{base_url}/wapi/object_type/list".format(scheme=API_SCHEME, base_host=API_HOST,
base_url=API_BASE_URL)).json()[0]
swagger['tags'] = systems.json()[0]
swagger['securityDefinitions'] = {'api_key': {'type': 'apiKey', 'name': 'Authorization', 'in': 'header'}}
swagger['paths'] = {f"/{f['system']}/{f['object_type']}/{f['name']}":
{
'post' if f['is_data_manipulating'] else 'get': {
'parameters': generateParamters(f),
'produces': ['application/json'] if f['is_returning'] else [],
'tags': [f['system']],
'security': [{'api_key': []}],
'responses': {
200: {
'description': 'Request erfolgreich',
'schema': {
'type': 'array',
'items': {
'$ref': '#/definitions/' + f['object_type']
}
} if f['is_returning'] else None
},
400: {
'description': 'Eingabefehler',
},
401: {
'description': 'Unautorisiert',
},
500: {
'description': 'Interner Serverfehler',
},
}}}
for f in func_index
}
def renderDataType(d):
dat = {'type': d['dataType']['jsonName'],
'description': d['description']
}
if d['dataType']['apiName'] == 'text_array':
dat['items'] = {
'type': 'string'
}
return dat
swagger['definitions'] = {o['name']:
{
'type': 'object',
'description': o['description'],
'properties': {
a: renderDataType(d)
for a, d in o['attributes'].items()
}
}
for o in objects
}
with open(SWAGGER_TARGET_FILE, 'w') as f:
f.write(json.dumps(swagger))
This diff is collapsed.
......@@ -19,9 +19,10 @@
"http-proxy-middleware": "^0.19.1",
"i": "^0.3.6",
"node-sass": "^4.11.0",
"npm": "^6.12.0",
"npm": "^6.13.2",
"popper.js": "^1.14.7",
"sass-loader": "^7.1.0",
"swagger-ui": "^3.24.3",
"v-debounce": "^0.1.2",
"vue": "^2.6.0",
"vue-awesome": "^3.4.0",
......
api.json
\ No newline at end of file
......@@ -62,7 +62,7 @@
<hr>
<p class="pull-right d-print-none">
<a target="_blank" href="https://www-net-doku.scc.kit.edu/webapi/release/dnscfg-perms/"><font-awesome-icon :icon="['far', 'life-ring']"></font-awesome-icon> Hilfe/Dokumentation</a> |
<a target="_blank" href="https://www-net-devel-doku.scc.kit.edu/webapi/release/intro/"><font-awesome-icon icon="code"></font-awesome-icon> API</a> |
<b-link to="/swagger"><font-awesome-icon icon="code"></font-awesome-icon> API</b-link> |
<a target="_blank" href="https://git.scc.kit.edu/scc-net/net-suite/net-suite/issues"><font-awesome-icon icon="bug"></font-awesome-icon> Fehler melden</a> |
Kontakt: <a href="mailto:dns-betrieb@scc.kit.edu">dns-betrieb∂scc.kit.edu</a>
</p>
......
......@@ -10,6 +10,16 @@ export default new Router({
{
path: '/', beforeEnter: (to, from, next) => next('/dnsvs')
},
{
path: '/swagger',
name: 'wagger-ui',
component: () => import('./views/swagger/ui.vue'),
meta: {
resolveName: function () {
return "API-Browser"
}
}
},
{
path: '/login',
name: 'login',
......
<template>
<div class="main">
<h1>Accounts</h1>
<b-modal id="modal-create-account" title="Subaccount erstellen" @ok="createAccount">
<b-modal id="modal-create-account" size="lg" title="Subaccount erstellen" @ok="createAccount">
<b-form @submit="createAccount">
<b-form-group label="Login Name:" label-for="input-account-create-login-name">
<b-form-input
......@@ -25,7 +25,7 @@
<b-button variant="outline-secondary" @click="cancel()">
Abbrechen
</b-button>
<b-button variant="success" @click="ok()">
<b-button type="submit" variant="success" @click="ok()">
Erstellen
</b-button>
</template>
......@@ -48,7 +48,7 @@
</b-button>
</b-col>
</b-row>
<b-modal id="modal-edit-account" title="Account bearbeiten">
<b-modal id="modal-edit-account" size="lg" title="Account bearbeiten">
<b-form>
<b-form-group label="Login Name:" label-for="input-account-edit-login-name">
<b-form-input
......@@ -73,7 +73,7 @@
Abbrechen
</b-button>
<!-- TODO: don't show for main account -->
<b-button variant="danger">
<b-button v-if="new_account.parent_login_name !== null" variant="danger">
Löschen
</b-button>
<b-button variant="primary" @click="ok()">
......@@ -81,7 +81,7 @@
</b-button>
</template>
</b-modal>
<b-modal id="modal-create-token" title="Token erstellen">
<b-modal id="modal-create-token" size=lg title="Token erstellen">
<b-form>
<b-form-group label="Beschreibung:" label-for="input-token-create-description">
<b-form-textarea
......@@ -92,18 +92,33 @@
invalid-feedback="TODO: Beschreibung bad!"
/>
</b-form-group>
<b-form-group label="Ablaufdatum:" label-for="input-token-create-delete_date">
<!-- TODO: use theme color -->
<VueCtkDateTimePicker
:inline="!isMobile()"
:min-date="getMinDate()"
format="DD-MM-YYYY HH:mm"
locale="de"
noButtonNow
minute-interval="5"
noLabel
noClearButton
id="input-token-create-delete_date"
color="#007BFF"
button-color="#007BFF"
v-model="test_date"/>
</b-form-group>
</b-form>
<p>TODO: Date picker / Ablaufdatum</p>
<template v-slot:modal-footer="{cancel, ok}">
<b-button variant="outline-secondary" @click="cancel()">
Abbrechen
</b-button>
<b-button variant="success" @click="ok()">
<b-button type="submit" variant="success" @click="ok()">
Erstellen
</b-button>
</template>
</b-modal>
<b-modal id="modal-edit-token" title="Token bearbeiten">
<b-modal id="modal-edit-token" size=lg title="Token bearbeiten">
<b-form>
<b-form-group label="Beschreibung:" label-for="input-token-edit-description">
<b-form-textarea
......@@ -115,7 +130,8 @@
/>
</b-form-group>
</b-form>
<p>TODO: Date picker / Ablaufdatum</p>
<!-- TODO: use theme color -->
<VueCtkDateTimePicker color="#007BFF" v-model="test_date"/>
<template v-slot:modal-footer="{cancel, ok}">
<b-button variant="outline-secondary" @click="cancel()">
Abbrechen
......@@ -123,7 +139,7 @@
<b-button variant="danger">
Löschen
</b-button>
<b-button variant="primary" @click="ok()">
<b-button type="submit" variant="primary" @click="ok()">
Änderungen übernehmen
</b-button>
</template>
......@@ -136,15 +152,18 @@
<h4>
{{account.login_name}}
<b-badge variant="success">
<div v-if="tokens_by_account[account.login_name].length === 1">{{tokens_by_account[account.login_name].length}} Token</div>
<div v-if="tokens_by_account[account.login_name].length === 1">
{{tokens_by_account[account.login_name].length}} Token
</div>
<div v-else>{{tokens_by_account[account.login_name].length}} Tokens</div>
</b-badge>
</h4>
<p class="text-muted">Login Name</p>
</b-col>
<b-col>
TODO: "Hauptaccount, kann nicht gelöcht werden." / "Keine Beschreibung vorhanden."
{{account.description}}
<div v-if="account.parent_login_name == null">Hauptaccount; enthält nur Session-Tokens; kann nicht gelöscht werden.</div>
<div v-else-if="account.description == null">Keine Beschreibung vorhanden.</div>
<div v-else>{{account.description}}</div>
<p class="text-muted">Beschreibung</p>
</b-col>
<b-col>
......@@ -211,9 +230,11 @@
name: 'tokens',
data() {
return {
test_date: null,
tokens_by_account: null,
accounts: null,
new_account: {
parent_login_name: null,
description: '',
login_name: '',
delete_date: null
......@@ -285,6 +306,26 @@
})
},
methods: {
openModalEditAccount(account) {
this.new_account.description = account.description
this.new_account.login_name = account.login_name
this.new_account.delete_date = account.delete_date
},
openModalEditToken(token) {
this.new_token.description = token.description
this.new_token.login_name = token.login_name
this.new_token.delete_date = token.delete_date
},
resetNewAccount() {
this.new_account.description = ''
this.new_account.login_name = ''
this.new_account.delete_date = null
},
resetNewToken() {
this.new_token.description = ''
this.new_token.login_name = ''
this.new_token.delete_date = null
},
createAccount() {
AccountService.create(this.$store.state.netdb_axios_config, {
description_new: this.new_account.description
......@@ -292,6 +333,11 @@
},
formatDate(value) {
return new Date(Date.parse(value)).toLocaleString('de-DE');
},
getMinDate() {
let d = new Date()
d.setDate(d.getDate() - 1)
return d
}
}
}
......
<template>
<div id="swagger-ui">
</div>
</template>
<script>
import SwaggerUI from 'swagger-ui'
import 'swagger-ui/dist/swagger-ui.css'
export default {
name: "ui.vue",
mounted() {
var self = this
SwaggerUI({
url: '/api.json',
docExpansion: 'none',
dom_id: '#swagger-ui',
deepLinking: false,
presets: [
SwaggerUI.presets.apis
],
plugins: [
SwaggerUI.plugins.DownloadUrl
],
requestInterceptor: function (req) {
if (self.$store.state.logged_in) {
req.headers.Authorization = self.$store.state.token.token
window.console.log('Authorized from Session');
}
},
})
},
created() {
}
}
</script>
<style scoped>
</style>
......@@ -853,6 +853,54 @@ class NDPPort(DBObject):
self.name = name
self.module = module
@staticmethod
def get_nd_p_port_by_vlan(db, connection, vlan_id, ni_name):
res = db.execute(connection,
"""
select nd_mdl_typ_class.name as root_nd_mdl_typ_class_name, root_typ.name as root_nd_mdl_typ_name, dst.name as nd_p_port_name, root.name as root_nd_mdl_name, mod.name as base_nd_mdl_name,
nd_room.name as nd_room_name, nd_room.nr as nd_room_nr, nd_floor.nr as nd_floor_nr, nd_floor.descr as nd_floor_descr,
nd_bldg.nr as nd_bldg_nr, nd_bldg.name as nd_bldg_name from nd_dev
INNER JOIN dns_ntree on nd_dev.dns_ntree_key_nr=dns_ntree.key_nr
inner join nd_l_port on nd_dev.key_nr=nd_l_port.nd_dev_key_nr
inner join nd_vlan on nd_l_port.nd_vlan_key_nr=nd_vlan.key_nr
inner join nd_net_instnc on nd_vlan.nd_net_instnc_key_nr=nd_net_instnc.key_nr
inner join nd_l2p_port on nd_l_port.key_nr=nd_l2p_port.nd_l_port_key_nr
inner join nd_p_port src on nd_l2p_port.nd_p_port_key_nr = src.key_nr
inner join nd_p_port dst on src.nd_dest_p_port_key_nr=dst.key_nr
inner join nd_mdl mod on dst.nd_mdl_key_nr=mod.key_nr
inner join nd_mdl root on mod.nd_root_mdl_key_nr=root.key_nr
inner join nd_room on root.nd_room_key_nr=nd_room.key_nr
inner join nd_floor on nd_room.nd_floor_key_nr=nd_floor.key_nr
inner join nd_bldg on nd_room.nd_bldg_key_nr=nd_bldg.key_nr
inner join nd_mdl_typ root_typ on root.nd_mdl_typ_key_nr = root_typ.key_nr
inner join nd_mdl_typ_class on root_typ.nd_mdl_typ_class_key_nr=nd_mdl_typ_class.key_nr
where nd_net_instnc.name = {ni_name} and nd_vlan.id = {vlan_id}
""",
{'ni_name': ni_name, 'vlan_id': vlan_id})
if len(res) == 0:
return None
return [NDPPort(
**_marshal_dict(r, NDPPort),
module=NDModule(**_marshal_dict(_marshal_dict_str(r, 'base'), NDModule),
root_module=NDModule(**_marshal_dict(_marshal_dict_str(r, 'root'), NDModule),
room=NDRoom(
**_marshal_dict(r, NDRoom)),
floor=NDFloor(
**_marshal_dict(r, NDFloor)),
building=NDBuilding(
**_marshal_dict(r, NDBuilding)),
module_type=NDModuleType(
**_marshal_dict(_marshal_dict_str(r, 'root'), NDModuleType),
type_class=NDModuleTypeClass(
**_marshal_dict(_marshal_dict_str(r, 'root'),
NDModuleTypeClass)
)
)
),
)
) for r in res]
@staticmethod
def get_nd_p_port_by_dev_fqdn_and_port_name(db, connection, fqdn, port):
res = db.execute(connection,
......
from flask import Blueprint
from net_suite import app, get_git_version, ModMetaData
import requests
# we are assuming, that at the time of loading the app is initializes and the config is loaded
bb_name = 'user_nd'
user_nd = Blueprint(bb_name, __name__)
METADATA = ModMetaData(name=bb_name, mod_path=__name__, gitlab_url='https://git.scc.kit.edu/scc-net/net-suite/net-suite',
printable_name='User-Netdoc', version=get_git_version(__file__),
contact_email='dns-betrieb@scc.kit.edu', is_tool=False)
MENU = []
from . import views
from . import user_nd, METADATA
from net_suite.model import *
from net_suite import app
from net_suite.views import login_required, get_db_conn, db
import requests
from flask import render_template, request, flash, jsonify, redirect, url_for, abort
@user_nd.route('/vlans/<ni_name>/<vlan>/ports')
@login_required
def get_dev_ports(ni_name, vlan):
vlan = int(vlan)
s = request.environ['beaker.session']
areas = s['login'].get_areas(db=db, connection=get_db_conn())
vlan_valid = False
for a in areas:
if a.vlan is not None and a.vlan.id == vlan and a.vlan.ni_name == ni_name:
vlan_valid = True
break
if not vlan_valid:
return abort(404)
return jsonify(NDPPort.get_nd_p_port_by_vlan(db, get_db_conn(), ni_name=ni_name, vlan_id=vlan))
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment