feat(lasuite): add La Gaufre integration service
Deploys the suitenumerique/lasuite-integration app that serves the La Gaufre app launcher (gaufre.js) and acts as the federation hub for the La Suite Numérique app switching menu. The service runs at integration.DOMAIN_SUFFIX and exposes /api/v1/gaufre.js — referenced by docs, people, and other La Suite apps via GAUFREJS_URL to render the unified app switcher.
This commit is contained in:
328
base/lasuite/integration-deployment.yaml
Normal file
328
base/lasuite/integration-deployment.yaml
Normal file
@@ -0,0 +1,328 @@
|
||||
# La Gaufre integration service — O Estúdio app launcher.
|
||||
# Serves a custom gaufre.js widget and services.json for our app list.
|
||||
# Apps set GAUFREJS_URL=https://integration.DOMAIN_SUFFIX/api/v1/gaufre.js
|
||||
# which loads this widget; clicking it fetches /api/v1/services.json.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: integration-config
|
||||
namespace: lasuite
|
||||
data:
|
||||
services.json: |
|
||||
[
|
||||
{
|
||||
"id": "documentos",
|
||||
"name": "Documentos",
|
||||
"url": "https://docs.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "ficheiros",
|
||||
"name": "Ficheiros",
|
||||
"url": "https://drive.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "reunioes",
|
||||
"name": "Reuniões",
|
||||
"url": "https://meet.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "conversas",
|
||||
"name": "Conversas",
|
||||
"url": "https://chat.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "correio",
|
||||
"name": "Correio",
|
||||
"url": "https://mail.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "pessoas",
|
||||
"name": "Pessoas",
|
||||
"url": "https://people.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "descobrir",
|
||||
"name": "Descobrir",
|
||||
"url": "https://find.DOMAIN_SUFFIX"
|
||||
},
|
||||
{
|
||||
"id": "administrar",
|
||||
"name": "Administrar",
|
||||
"url": "https://admin.DOMAIN_SUFFIX"
|
||||
}
|
||||
]
|
||||
|
||||
gaufre.js: |
|
||||
/**
|
||||
* O Estúdio — La Gaufre widget (self-hosted).
|
||||
* Drop-in replacement for the official integration.lasuite.numerique.gouv.fr widget.
|
||||
* Fetches services.json from its own origin and renders a DSFR-style popup.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const origin = (function () {
|
||||
const s = document.currentScript;
|
||||
if (s && s.src) {
|
||||
const u = new URL(s.src);
|
||||
return u.origin;
|
||||
}
|
||||
return window.location.origin;
|
||||
})();
|
||||
|
||||
const SERVICES_URL = origin + '/api/v1/services.json';
|
||||
const SUITE_NAME = 'O Estúdio';
|
||||
|
||||
const STYLE = `
|
||||
#estudio-gaufre-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
#estudio-gaufre-overlay.open {
|
||||
display: block;
|
||||
}
|
||||
#estudio-gaufre-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
#estudio-gaufre-panel {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
left: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,.15);
|
||||
padding: 16px;
|
||||
width: 320px;
|
||||
z-index: 1;
|
||||
}
|
||||
#estudio-gaufre-panel h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
margin: 0 0 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
#estudio-gaufre-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.estudio-gaufre-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 12px 8px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: background .15s;
|
||||
}
|
||||
.estudio-gaufre-app:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.estudio-gaufre-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: #e8eaf6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ICONS = {
|
||||
documentos: '📄',
|
||||
ficheiros: '📁',
|
||||
reunioes: '🎥',
|
||||
conversas: '💬',
|
||||
correio: '✉️',
|
||||
pessoas: '👥',
|
||||
descobrir: '🔍',
|
||||
administrar: '⚙️',
|
||||
};
|
||||
|
||||
function injectStyles() {
|
||||
const el = document.createElement('style');
|
||||
el.textContent = STYLE;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
|
||||
function buildPanel(services) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'estudio-gaufre-overlay';
|
||||
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.id = 'estudio-gaufre-backdrop';
|
||||
backdrop.addEventListener('click', close);
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'estudio-gaufre-panel';
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.textContent = SUITE_NAME;
|
||||
panel.appendChild(title);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.id = 'estudio-gaufre-grid';
|
||||
|
||||
services.forEach(function (svc) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'estudio-gaufre-app';
|
||||
a.href = svc.url;
|
||||
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'estudio-gaufre-icon';
|
||||
icon.textContent = ICONS[svc.id] || '🔲';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = svc.name;
|
||||
|
||||
a.appendChild(icon);
|
||||
a.appendChild(label);
|
||||
grid.appendChild(a);
|
||||
});
|
||||
|
||||
panel.appendChild(grid);
|
||||
overlay.appendChild(backdrop);
|
||||
overlay.appendChild(panel);
|
||||
document.body.appendChild(overlay);
|
||||
return overlay;
|
||||
}
|
||||
|
||||
let overlay = null;
|
||||
let services = null;
|
||||
|
||||
function open() {
|
||||
if (!services) {
|
||||
fetch(SERVICES_URL)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
services = data;
|
||||
overlay = buildPanel(services);
|
||||
overlay.classList.add('open');
|
||||
});
|
||||
} else {
|
||||
if (!overlay) overlay = buildPanel(services);
|
||||
overlay.classList.add('open');
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (overlay) overlay.classList.remove('open');
|
||||
}
|
||||
|
||||
function init() {
|
||||
injectStyles();
|
||||
document.querySelectorAll('.js-lasuite-gaufre-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
overlay && overlay.classList.contains('open') ? close() : open();
|
||||
});
|
||||
});
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') close();
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
nginx.conf: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location = /api/v1/services.json {
|
||||
alias /etc/integration/services.json;
|
||||
add_header Content-Type "application/json; charset=utf-8";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
|
||||
location = /api/v1/gaufre.js {
|
||||
alias /etc/integration/gaufre.js;
|
||||
add_header Content-Type "application/javascript; charset=utf-8";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: integration
|
||||
namespace: lasuite
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: integration
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: integration
|
||||
spec:
|
||||
containers:
|
||||
- name: integration
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/integration
|
||||
- name: nginx-conf
|
||||
mountPath: /etc/nginx/conf.d/default.conf
|
||||
subPath: nginx.conf
|
||||
resources:
|
||||
limits:
|
||||
memory: 32Mi
|
||||
requests:
|
||||
memory: 16Mi
|
||||
cpu: 5m
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: integration-config
|
||||
items:
|
||||
- key: services.json
|
||||
path: services.json
|
||||
- key: gaufre.js
|
||||
path: gaufre.js
|
||||
- name: nginx-conf
|
||||
configMap:
|
||||
name: integration-config
|
||||
items:
|
||||
- key: nginx.conf
|
||||
path: nginx.conf
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: integration
|
||||
namespace: lasuite
|
||||
spec:
|
||||
selector:
|
||||
app: integration
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
Reference in New Issue
Block a user