Show the licence, attribution and source offer in the interface
Two obligations the licence creates had nowhere to land. The attribution term requires the notice to stay visible in the Appropriate Legal Notices a work displays, and section 13 owes the Corresponding Source to anyone interacting with the program over a network - neither is discharged by a file sitting in a repository the user of a deployment never sees. So the About dialog is not decoration: it is the only place either obligation is met, which is why the component says so in a comment. A deployment that drops it stops complying with the licence it ships under, and the attribution term would be asking of others something this interface does not do itself. The login page sits outside this layout, so an unauthenticated visitor still sees none of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
33150978aa
commit
4e1926588b
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
-->
|
||||
|
||||
<div class="main-layout flex h-screen flex-col">
|
||||
<mat-toolbar class="app-topbar !fixed !left-0 !right-0 !top-0 !z-50 !bg-[#d8f4e7] !text-slate-800">
|
||||
<div class="flex items-center gap-4">
|
||||
|
|
@ -31,6 +37,10 @@
|
|||
<span>Admin users</span>
|
||||
</button>
|
||||
}
|
||||
<button mat-menu-item type="button" (click)="openAboutDialog()">
|
||||
<mat-icon fontIcon="info"></mat-icon>
|
||||
<span>About</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
@if (changePasswordSuccess()) {
|
||||
|
|
@ -40,6 +50,10 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
@if (aboutOpen()) {
|
||||
<app-about-dialog (closed)="closeAboutDialog()"></app-about-dialog>
|
||||
}
|
||||
|
||||
@if (changePasswordOpen() && loggedUser()?.username; as username) {
|
||||
<app-change-password-dialog
|
||||
[username]="username"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
|
||||
import { afterNextRender, ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
|
@ -9,12 +13,13 @@ import { ChangePasswordRequest } from '@models/user';
|
|||
import { Authorization } from '@services/authorization/authorization';
|
||||
import { BlocksService } from '@services/blocks/blocks';
|
||||
import { ContainersService } from '@services/containers/containers';
|
||||
import { AboutDialogComponent } from '@shared/about-dialog/about-dialog';
|
||||
import { ChangePasswordDialogComponent } from '@shared/change-password-dialog/change-password-dialog';
|
||||
import { scheduleSignalClear } from '@utilities/temporary-signal';
|
||||
|
||||
@Component({
|
||||
selector: 'app-app-layout',
|
||||
imports: [RouterOutlet, RouterLink, RouterLinkActive, ChangePasswordDialogComponent, MatButtonModule, MatIconModule, MatMenuModule, MatTabsModule, MatToolbarModule],
|
||||
imports: [RouterOutlet, RouterLink, RouterLinkActive, AboutDialogComponent, ChangePasswordDialogComponent, MatButtonModule, MatIconModule, MatMenuModule, MatTabsModule, MatToolbarModule],
|
||||
templateUrl: './app-layout.html',
|
||||
styleUrl: './app-layout.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
@ -28,6 +33,7 @@ export class AppLayout {
|
|||
private containersService = inject(ContainersService);
|
||||
|
||||
loggedUser = this.authService.loggedInUser;
|
||||
aboutOpen = signal(false);
|
||||
changePasswordOpen = signal(false);
|
||||
changePasswordSaving = signal(false);
|
||||
changePasswordError = signal<string | null>(null);
|
||||
|
|
@ -58,6 +64,14 @@ logout() {
|
|||
});
|
||||
}
|
||||
|
||||
openAboutDialog() {
|
||||
this.aboutOpen.set(true);
|
||||
}
|
||||
|
||||
closeAboutDialog() {
|
||||
this.aboutOpen.set(false);
|
||||
}
|
||||
|
||||
openChangePasswordDialog() {
|
||||
if (!this.loggedUser()?.username?.trim()) return;
|
||||
this.changePasswordError.set(null);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
*/
|
||||
|
||||
/* Matches the chrome of the password dialogs; kept separate because this one is not a form. */
|
||||
.about-dialog-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
}
|
||||
|
||||
.about-dialog-modal {
|
||||
width: min(560px, calc(100vw - 32px));
|
||||
max-height: min(80vh, 720px);
|
||||
overflow: auto;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 20px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 24px 64px rgba(15, 23, 42, 0.28);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.about-dialog-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.about-dialog-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.about-dialog-subtitle {
|
||||
margin: 4px 0 0;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.about-dialog-body {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.6;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.about-dialog-body p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.about-dialog-section-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.about-dialog-copyright {
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.about-dialog-affiliation {
|
||||
font-size: 12.5px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.about-dialog-note {
|
||||
font-size: 12.5px;
|
||||
color: #64748b;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.about-dialog-attribution {
|
||||
margin: 0;
|
||||
padding: 10px 14px;
|
||||
border-left: 3px solid #2563eb;
|
||||
border-radius: 0 8px 8px 0;
|
||||
background: #f1f5f9;
|
||||
font-style: italic;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.about-dialog-links {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.about-dialog-links li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.about-dialog-link-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.about-dialog-body a {
|
||||
color: #1d4ed8;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.about-dialog-body code {
|
||||
padding: 1px 5px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 4px;
|
||||
background: #f1f5f9;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
-->
|
||||
|
||||
<div class="about-dialog-backdrop" (click)="close($event)">
|
||||
<div class="about-dialog-modal" (click)="$event.stopPropagation()">
|
||||
<div class="about-dialog-header">
|
||||
<div>
|
||||
<h3 class="about-dialog-title">About HumAIn Flow</h3>
|
||||
<p class="about-dialog-subtitle">Licence, attribution and source code</p>
|
||||
</div>
|
||||
<button type="button" mat-stroked-button (click)="close($event)">Close</button>
|
||||
</div>
|
||||
|
||||
<div class="about-dialog-body">
|
||||
<section class="about-dialog-section">
|
||||
<p class="about-dialog-copyright">{{ copyright }}</p>
|
||||
<p class="about-dialog-affiliation">{{ affiliation }}</p>
|
||||
</section>
|
||||
|
||||
<section class="about-dialog-section">
|
||||
<h4 class="about-dialog-section-title">Licence</h4>
|
||||
<p>
|
||||
HumAIn Flow is free software, released under the
|
||||
<a [href]="licenseUrl" target="_blank" rel="noopener noreferrer">{{ licenseName }}</a>,
|
||||
with an additional attribution term under section 7(b) of that licence.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="about-dialog-section">
|
||||
<h4 class="about-dialog-section-title">Attribution</h4>
|
||||
<p class="about-dialog-note">
|
||||
This notice must be preserved in any work based on HumAIn Flow.
|
||||
</p>
|
||||
<blockquote class="about-dialog-attribution">{{ attribution }}</blockquote>
|
||||
</section>
|
||||
|
||||
<section class="about-dialog-section">
|
||||
<h4 class="about-dialog-section-title">Source code</h4>
|
||||
<p class="about-dialog-note">
|
||||
You are entitled to the complete source of the version you are using, including any
|
||||
modification made to it.
|
||||
</p>
|
||||
<ul class="about-dialog-links">
|
||||
@for (source of sources; track source.url) {
|
||||
<li>
|
||||
<mat-icon class="about-dialog-link-icon" fontIcon="code"></mat-icon>
|
||||
<span>{{ source.label }}:</span>
|
||||
<a [href]="source.url" target="_blank" rel="noopener noreferrer">{{ source.url }}</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="about-dialog-section">
|
||||
<h4 class="about-dialog-section-title">Citation</h4>
|
||||
<p>
|
||||
If you use HumAIn Flow in academic work, please cite it. The citation metadata is in the
|
||||
<code>CITATION.cff</code> file of each repository.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
|
||||
import { ChangeDetectionStrategy, Component, output } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
/**
|
||||
* The application's Appropriate Legal Notices, in the sense section 0 of the GNU AGPL gives that
|
||||
* term.
|
||||
*
|
||||
* This is not decoration. Two obligations are discharged here and nowhere else in the interface:
|
||||
* the attribution that LICENSE-ADDENDUM requires every derivative to keep visible to its users,
|
||||
* and the offer of the Corresponding Source that section 13 owes to anyone interacting with the
|
||||
* program over a network. Changing the wording is fine; removing either one puts the deployment
|
||||
* out of compliance with the licence it ships under.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-about-dialog',
|
||||
imports: [MatButtonModule, MatIconModule],
|
||||
templateUrl: './about-dialog.html',
|
||||
styleUrl: './about-dialog.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutDialogComponent {
|
||||
readonly closed = output<void>();
|
||||
|
||||
readonly copyright = 'Copyright © 2025-2026 Lucio Lelii';
|
||||
readonly affiliation =
|
||||
'Institute of Information Science and Technologies "A. Faedo" (ISTI-CNR), Pisa, Italy';
|
||||
|
||||
readonly attribution = 'Based on HumAIn Flow, originally developed by Lucio Lelii (ISTI-CNR)';
|
||||
|
||||
readonly licenseName = 'GNU Affero General Public License v3.0 or later';
|
||||
readonly licenseUrl = 'https://www.gnu.org/licenses/agpl-3.0.html';
|
||||
|
||||
readonly sources = [
|
||||
{ label: 'Web interface', url: 'https://github.com/luciolelii/humanaiFlow-angular' },
|
||||
{ label: 'Backend service', url: 'https://github.com/luciolelii/humainflow-service' }
|
||||
];
|
||||
|
||||
close(event?: Event) {
|
||||
event?.stopPropagation();
|
||||
this.closed.emit();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue