Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel Status #354

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions api/lib/api/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import TAKAPI from '../tak-api.js';
import { Type, Static } from '@sinclair/typebox';
import { Group } from './groups.js';
import { TAKList } from './types.js';

export const Subscription = Type.Object({
dn: Type.Union([Type.String(), Type.Null()]),
callsign: Type.String(),
clientUid: Type.String(),
lastReportMilliseconds: Type.Integer(),
takClient: Type.String(),
takVersion: Type.String(),
username: Type.String(),
groups: Type.Array(Group),
role: Type.String(),
team: Type.String(),
ipAddress: Type.String(),
port: Type.String(),
pendingWrites: Type.Integer(),
numProcessed: Type.Integer(),
protocol: Type.String(),
xpath: Type.Union([Type.String(), Type.Null()]),
subscriptionUid: Type.String(),
appFramerate: Type.String(),
battery: Type.String(),
batteryStatus: Type.String(),
batteryTemp: Type.String(),
deviceDataRx: Type.String(),
deviceDataTx: Type.String(),
heapCurrentSize: Type.String(),
heapFreeSize: Type.String(),
heapMaxSize: Type.String(),
deviceIPAddress: Type.String(),
storageAvailable: Type.String(),
storageTotal: Type.String(),
incognito: Type.Boolean(),
handlerType: Type.String(),
lastReportDiffMilliseconds: Type.Integer()
});

export const ListSubscriptionInput = Type.Object({
sortBy: Type.String({
default: 'CALLSIGN',
enum: ['CALLSIGN', 'UID']
}),
direction: Type.String({
default: 'ASCENDING',
enum: ['ASCENDING', 'DESCENDING']
}),
page: Type.Integer({
default: -1
}),
limit: Type.Integer({
default: -1
})
})

export default class {
api: TAKAPI;

constructor(api: TAKAPI) {
this.api = api;
}

async list(
query: Static<typeof ListSubscriptionInput>
): Promise<TAKList<Static<typeof Subscription>>> {
const url = new URL(`/Marti/api/subscriptions/all`, this.api.url);

let q: keyof Static<typeof ListSubscriptionInput>;
for (q in query) {
if (query[q] !== undefined) {
url.searchParams.append(q, String(query[q]));
}
}

return await this.api.fetch(url, {
method: 'GET'
});
}
}
3 changes: 3 additions & 0 deletions api/lib/tak-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Credentials from './api/credentials.js';
import Contacts from './api/contacts.js';
import Files from './api/files.js';
import Group from './api/groups.js';
import Subscription from './api/subscriptions.js';
import Video from './api/video.js';
import Export from './api/export.js';
import Err from '@openaddresses/batch-error';
Expand All @@ -28,6 +29,7 @@ export default class TAKAPI {
MissionLayer: MissionLayer;
Credentials: Credentials;
Contacts: Contacts;
Subscription: Subscription;
Group: Group;
Video: Video;
Export: Export;
Expand All @@ -45,6 +47,7 @@ export default class TAKAPI {
this.MissionLayer = new MissionLayer(this);
this.Credentials = new Credentials(this);
this.Contacts = new Contacts(this);
this.Subscription = new Subscription(this);
this.Group = new Group(this);
this.Video = new Video(this);
this.Files = new Files(this);
Expand Down
1,308 changes: 1,110 additions & 198 deletions api/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"@types/busboy": "^1.5.0",
"@types/connect-history-api-fallback": "^1.3.5",
"@types/cors": "^2.8.13",
"@types/express": "^5.0.0",
"@types/express": "^4.0.0",
"@types/geojson": "^7946.0.10",
"@types/jsonwebtoken": "^9.0.1",
"@types/memjs": "^1.3.0",
Expand Down
93 changes: 93 additions & 0 deletions api/routes/marti-subscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Type } from '@sinclair/typebox'
import Schema from '@openaddresses/batch-schema';
import Err from '@openaddresses/batch-error';
import Auth from '../lib/auth.js';
import Config from '../lib/config.js';
import { Subscription, ListSubscriptionInput } from '../lib/api/subscriptions.js'
import TAKAPI, {
APIAuthCertificate
} from '../lib/tak-api.js';

export default async function router(schema: Schema, config: Config) {
await schema.get('/marti/subscription', {
name: 'List Subscriptions',
group: 'MartiSubscription',
description: 'Helper API to list subscriptions that the client can see',
query: ListSubscriptionInput,
res: Type.Object({
version: Type.String(),
type: Type.String(),
data: Type.Array(Subscription),
messages: Type.Optional(Type.Array(Type.String())),
nodeId: Type.Optional(Type.String())
})
}, async (req, res) => {
try {
await Auth.is_auth(config, req);

const user = await Auth.as_user(config, req);
const profile = await config.models.Profile.from(user.email);
const api = await TAKAPI.init(new URL(String(config.server.api)), new APIAuthCertificate(profile.auth.cert, profile.auth.key));

const groups: Set<string> = new Set();
(await api.Group.list()).data.forEach((group) => {
groups.add(group.name)
});

const subs = await api.Subscription.list(req.query);

subs.data.forEach((sub) => {
return sub.groups.filter((group) => {
return groups.has(group.name);
})
});

return res.json(subs);
} catch (err) {
return Err.respond(err, res);
}
});

await schema.get('/marti/subscription/:clientuid', {
name: 'Get Subscription',
group: 'MartiSubscription',
description: 'Helper API to get a subscription',
params: Type.Object({
clientuid: Type.String()
}),
res: Subscription
}, async (req, res) => {
try {
await Auth.is_auth(config, req);

const user = await Auth.as_user(config, req);
const profile = await config.models.Profile.from(user.email);
const api = await TAKAPI.init(new URL(String(config.server.api)), new APIAuthCertificate(profile.auth.cert, profile.auth.key));

const subs = await api.Subscription.list({
sortBy: 'CALLSIGN',
direction: 'ASCENDING',
page: -1,
limit: -1
});

const groups: Set<string> = new Set();
(await api.Group.list()).data.forEach((group) => {
groups.add(group.name)
});

for (const sub of subs.data) {
if (sub.clientUid === req.params.clientuid) {
sub.groups = sub.groups.filter((group) => {
return groups.has(group.name);
});
return res.json(sub);
}
}

throw new Err(404, null, `Subscription for ${req.params.clientuid} not found`);
} catch (err) {
return Err.respond(err, res);
}
});
}
2 changes: 1 addition & 1 deletion api/routes/marti.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default async function router(schema: Schema, config: Config) {
});

await schema.put('/marti/group', {
name: 'Upate Groups',
name: 'Update Groups',
group: 'Marti',
description: 'Helper API to update groups that the client is part of',
query: Type.Object({
Expand Down
31 changes: 30 additions & 1 deletion api/web/src/components/Admin/AdminServer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,35 @@
<div class='col-auto d-flex align-items-center'>
Once Certificates are uploaded they cannot be viewed
</div>
<div class='col-12 datagrid pt-2 pb-5'>
<div class='datagrid-item pb-2'>
<div class='datagrid-title'>
Certificate Valid From
</div>
<div
class='datagrid-content'
v-text='server.certificate.validFrom'
/>
</div>
<div class='datagrid-item pb-2'>
<div class='datagrid-title'>
Certificate Valid To
</div>
<div
class='datagrid-content'
v-text='server.certificate.validTo'
/>
</div>
<div class='datagrid-item pb-2'>
<div class='datagrid-title'>
Certificate Subject
</div>
<div
class='datagrid-content'
v-text='server.certificate.subject'
/>
</div>
</div>
<div
v-if='edit'
class='col-auto ms-auto'
Expand Down Expand Up @@ -150,7 +179,7 @@
</div>
<div
class='position-absolute bottom-0 w-100'
style='height: 61px;'
style='height: 53px;'
>
<div
v-if='server.updated'
Expand Down
Loading
Loading