Skip to content

Commit

Permalink
Merge pull request #354 from dfpc-coe/channel-status
Browse files Browse the repository at this point in the history
Channel Status
  • Loading branch information
ingalls authored Oct 1, 2024
2 parents 480b9d8 + 7630123 commit 57bc9e8
Show file tree
Hide file tree
Showing 10 changed files with 1,583 additions and 239 deletions.
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

0 comments on commit 57bc9e8

Please sign in to comment.