Skip to content

Commit

Permalink
Merge pull request #76 from hnez/serve-licenses
Browse files Browse the repository at this point in the history
 http_server: serve license files
  • Loading branch information
hnez authored Sep 10, 2024
2 parents 663855c + 1fe79f4 commit a519eb3
Show file tree
Hide file tree
Showing 12 changed files with 972 additions and 12 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/tacd-webui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,17 @@ jobs:
node-version: latest
- run: npm ci
- run: npm run build

test:
name: npm run test
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npm ci
- run: npm run test
9 changes: 9 additions & 0 deletions demo_files/usr/share/common-licenses/license.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PACKAGE NAME: tacd
PACKAGE VERSION: 0.1.0+gitAUTOINC+803b2084b2
RECIPE NAME: tacd
LICENSE: GPL-2.0-or-later

PACKAGE NAME: tacd-webinterface
PACKAGE VERSION: 0.1.0+gitAUTOINC+803b2084b2
RECIPE NAME: tacd-webinterface
LICENSE: GPL-2.0-or-later
339 changes: 339 additions & 0 deletions demo_files/usr/share/licenses/tacd-webinterface/LICENSE

Large diffs are not rendered by default.

339 changes: 339 additions & 0 deletions demo_files/usr/share/licenses/tacd/LICENSE

Large diffs are not rendered by default.

26 changes: 21 additions & 5 deletions src/http_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use serve_dir::serve_dir;
#[cfg(feature = "demo_mode")]
mod consts {
pub const WEBUI_DIR: &str = "web/build";
pub const LICENSE_DIR: &str = "demo_files/usr/share/licenses";
pub const LICENSE_MANIFEST: &str = "demo_files/usr/share/common-licenses/license.manifest";
pub const EXTRA_DIR: &str = "demo_files/srv/www";
pub const FS_PREFIX: &str = "demo_files";
pub const FALLBACK_PORT: &str = "[::]:8080";
Expand All @@ -37,12 +39,14 @@ mod consts {
#[cfg(not(feature = "demo_mode"))]
mod consts {
pub const WEBUI_DIR: &str = "/usr/share/tacd/webui";
pub const LICENSE_DIR: &str = "/usr/share/licenses";
pub const LICENSE_MANIFEST: &str = "/usr/share/common-licenses/license.manifest";
pub const EXTRA_DIR: &str = "/srv/www";
pub const FS_PREFIX: &str = "";
pub const FALLBACK_PORT: &str = "[::]:80";
}

use consts::{EXTRA_DIR, FALLBACK_PORT, FS_PREFIX, WEBUI_DIR};
use consts::{EXTRA_DIR, FALLBACK_PORT, FS_PREFIX, LICENSE_DIR, LICENSE_MANIFEST, WEBUI_DIR};

// openapi.json is generated by build.rs from openapi.yaml
const OPENAPI_JSON: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/openapi.json"));
Expand Down Expand Up @@ -78,8 +82,14 @@ impl HttpServer {
);

this.expose_openapi_json();
this.expose_dir(WEBUI_DIR, "/", false);
this.expose_dir(EXTRA_DIR, "/srv", true);
this.expose_dir(WEBUI_DIR, "/", false, None);
this.expose_dir(EXTRA_DIR, "/srv", true, None);
this.expose_dir(LICENSE_DIR, "/docs/legal/files", true, Some("text/plain"));

this.server
.at("/docs/legal/license.manifest")
.serve_file(LICENSE_MANIFEST)
.unwrap();

for (fs_path, web_path) in EXPOSED_FILES_RW {
let fs_path = FS_PREFIX.to_owned() + *fs_path;
Expand All @@ -103,8 +113,14 @@ impl HttpServer {
}

/// Serve a directory from disk for reading
fn expose_dir(&mut self, fs_path: &'static str, web_path: &str, directory_listings: bool) {
let handler = move |req| async move { serve_dir(fs_path, directory_listings, req).await };
fn expose_dir(
&mut self,
fs_path: &'static str,
web_path: &str,
directory_listings: bool,
force_mime: Option<&'static str>,
) {
let handler = move |req| serve_dir(req, fs_path, directory_listings, force_mime);

self.server.at(web_path).get(handler);
self.server.at(web_path).at("").get(handler);
Expand Down
19 changes: 14 additions & 5 deletions src/http_server/serve_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn clamp_timestamp(ts: SystemTime) -> SystemTime {
max(tacd_build_time, ts)
}

async fn file(req: &Request<()>, fs_path: &Path) -> Result {
async fn file(req: &Request<()>, fs_path: &Path, force_mime: Option<&str>) -> Result {
// Check the files modification date and compare it to the one provided
// by the client (if any) to determine if we even need to send the file.
let modification_date = fs_path.metadata()?.modified()?;
Expand Down Expand Up @@ -109,7 +109,11 @@ async fn file(req: &Request<()>, fs_path: &Path) -> Result {
.header("Last-Modified", last_modified)
.header("Cache-Control", "max-age=30, must-revalidate");

let body = Body::from_file(fs_path).await?;
let mut body = Body::from_file(fs_path).await?;

if let Some(mime) = force_mime {
body.set_mime(mime);
}

if have_gz && accept_gz {
let mut gz_body = Body::from_file(gz_path.unwrap()).await?;
Expand Down Expand Up @@ -241,7 +245,12 @@ fn dir_listing(fs_path: &Path, is_root: bool) -> Result {
Ok(res)
}

pub async fn serve_dir(base_path: &str, directory_listings: bool, req: Request<()>) -> Result {
pub async fn serve_dir(
req: Request<()>,
base_path: &str,
directory_listings: bool,
force_mime: Option<&str>,
) -> Result {
let url_path = req.url().path();
let has_trailing_slash = url_path.ends_with('/');

Expand Down Expand Up @@ -274,13 +283,13 @@ pub async fn serve_dir(base_path: &str, directory_listings: bool, req: Request<(

let res = {
if !is_dir {
file(&req, &path).await
file(&req, &path, force_mime).await
} else if !has_trailing_slash {
redirect_dir(url_path)
} else if directory_listings && !has_index {
dir_listing(&path, is_root)
} else {
file(&req, &index_path).await
file(&req, &index_path, force_mime).await
}
};

Expand Down
2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!@cloudscape-design)/\"",
"eject": "react-scripts eject"
},
"eslintConfig": {
Expand Down
5 changes: 4 additions & 1 deletion web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ function Navigation() {
{
type: "section",
text: "Documentation",
items: [{ type: "link", text: "REST API", href: "#/docs/api" }],
items: [
{ type: "link", text: "REST API", href: "#/docs/api" },
{ type: "link", text: "Legal Information", href: "#/docs/legal" },
],
},
{
type: "section",
Expand Down
5 changes: 5 additions & 0 deletions web/src/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ export default function LandingPage() {
href: "/#/docs/api",
description: "Find API definitions to automate you LXA TAC",
},
{
name: "Documentation / Legal Information",
href: "/#/docs/legal",
description: "See the software components and their licenses",
},
]}
/>
</SpaceBetween>
Expand Down
39 changes: 39 additions & 0 deletions web/src/Legal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from "node:fs";
import ReactDOM from "react-dom/client";

import { parse_manifest, package_table } from "./Legal";

const manifest_ref = [
{
package_name: "tacd",
version: "0.1.0+gitAUTOINC+803b2084b2",
recipe_name: "tacd",
license: "GPL-2.0-or-later",
},
{
package_name: "tacd-webinterface",
version: "0.1.0+gitAUTOINC+803b2084b2",
recipe_name: "tacd-webinterface",
license: "GPL-2.0-or-later",
},
];

it("parses the manifest", () => {
const manifest_raw = fs.readFileSync(
"../demo_files/usr/share/common-licenses/license.manifest",
"utf-8",
);

const manifest = parse_manifest(manifest_raw);

expect(manifest).toEqual(manifest_ref);
});

it("renders", () => {
const div = document.createElement("div");
const root = ReactDOM.createRoot(div);

const manifest_table = package_table(manifest_ref);

root.render(manifest_table);
});
185 changes: 185 additions & 0 deletions web/src/Legal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// This file is part of tacd, the LXA TAC system daemon
// Copyright (C) 2024 Pengutronix e.K.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import Container from "@cloudscape-design/components/container";
import Header from "@cloudscape-design/components/header";
import Table from "@cloudscape-design/components/table";
import Link from "@cloudscape-design/components/link";
import SpaceBetween from "@cloudscape-design/components/space-between";

import { useEffect, useState } from "react";

type Package = {
package_name: string;
version: string;
recipe_name: string;
license: string;
};

export function parse_manifest(text: string) {
let packages: Package[] = [];

// The content of `text` looks something like this:
//
// PACKAGE NAME: tacd
// PACKAGE VERSION: 1.0.0
// RECIPE NAME: tacd
// LICENSE: GPL-2.0-or-later
//
// PACKAGE NAME: tacd-webinterface
// ...

for (var group of text.split("\n\n")) {
let pkg: Package = {
package_name: "",
version: "",
recipe_name: "",
license: "",
};

for (var line of group.split("\n")) {
if (line.startsWith("PACKAGE NAME: ")) {
pkg.package_name = line.replace("PACKAGE NAME: ", "");
}
if (line.startsWith("PACKAGE VERSION: ")) {
pkg.version = line.replace("PACKAGE VERSION: ", "");
}
if (line.startsWith("RECIPE NAME: ")) {
pkg.recipe_name = line.replace("RECIPE NAME: ", "");
}
if (line.startsWith("LICENSE: ")) {
pkg.license = line.replace("LICENSE: ", "");
}
}

if (pkg.package_name && pkg.version && pkg.recipe_name && pkg.license) {
packages.push(pkg);
}
}

return packages;
}

export function package_table(packages?: Package[]) {
return (
<Table
header={
<Header
variant="h3"
description="Software packages used on this LXA TAC"
>
Packages
</Header>
}
columnDefinitions={[
{
id: "package_name",
header: "Package Name",
cell: (p) => p.package_name,
},
{
id: "version",
header: "Version",
cell: (p) => p.version,
},
{
id: "recipe_name",
header: "Recipe Name",
cell: (p) => p.recipe_name,
},
{
id: "license",
header: "License",
cell: (p) => (
<Link href={"/docs/legal/files/" + p.recipe_name}>{p.license}</Link>
),
},
]}
items={packages || []}
loading={packages === undefined}
sortingDisabled
resizableColumns
stickyHeader
trackBy="package_name"
/>
);
}

function PackageList() {
const [packages, setPackages] = useState<Package[]>();

useEffect(() => {
fetch("/docs/legal/license.manifest").then((response) => {
if (response.ok) {
response.text().then((text) => setPackages(parse_manifest(text)));
}
});
}, []);

return package_table(packages);
}

export default function Legal() {
return (
<SpaceBetween size="m">
<Header
variant="h1"
description="Information regarding your rights as an LXA TAC software user"
>
LXA TAC / Legal Information
</Header>

<Container
header={
<Header
variant="h2"
description="Where to find the source code that makes up the LXA TAC software"
>
Availability of Source Code
</Header>
}
>
<p>
The LXA TAC software uses many pieces of free and open source
software. A list of these pieces of software, along with their version
number and their respective software license, is shown below.
</p>

<p>
Linux Automation GmbH provides all software components required to
build your own LXA TAC software bundles in the form of a public Yocto
Layer:{" "}
<Link href="https://github.com/linux-automation/meta-lxatac">
linux-automation/meta-lxatac
</Link>
.
</p>

<p>
To comply with the terms of copyleft licenses like the GPL we also
provide copies of their sources, along with the applied patches on our{" "}
<Link href="https://downloads.linux-automation.com/lxatac/software/">
download server
</Link>
.
</p>
</Container>

<PackageList />
</SpaceBetween>
);
}
Loading

0 comments on commit a519eb3

Please sign in to comment.