Compare commits
No commits in common. "d448ca6516d26f69f872aafc378ce46e226c7a7f" and "41472ba17bdb3628bbdd598b68657d0cd9952040" have entirely different histories.
d448ca6516
...
41472ba17b
|
|
@ -2,8 +2,4 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
.env.prod
|
|
||||||
.env.stage
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
node_modules/
|
|
||||||
82
GEMINI.md
82
GEMINI.md
|
|
@ -1,82 +0,0 @@
|
||||||
# Avicenna Project Documentation
|
|
||||||
|
|
||||||
This document provides a comprehensive overview of the Avicenna project, its components, and instructions for development and deployment.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
The Avicenna project is a multi-component system composed of a web application, a data connector, a data transformation service, and the necessary infrastructure to run them. The project is divided into the following main parts:
|
|
||||||
|
|
||||||
- **service**: Contains the core application services, including the frontend dashboard and the backend data connector.
|
|
||||||
- **plattform**: Includes the data transformation and integration services.
|
|
||||||
- **infrastructure**: Defines the infrastructure as code for deploying the project.
|
|
||||||
|
|
||||||
## Services
|
|
||||||
|
|
||||||
### App Hub Dashboard (Angular Frontend)
|
|
||||||
|
|
||||||
The App Hub Dashboard is the user-facing web application for the Avicenna project, developed with Angular.
|
|
||||||
|
|
||||||
- **Location**: `service/app-hub/dashboard`
|
|
||||||
- **Key Files**:
|
|
||||||
- `angular.json`: Angular project configuration.
|
|
||||||
- `package.json`: Project dependencies and scripts.
|
|
||||||
- `tailwind.config.js`: Tailwind CSS configuration.
|
|
||||||
|
|
||||||
#### Commands
|
|
||||||
|
|
||||||
- **Run development server**: `ng serve`
|
|
||||||
- **Build for production**: `ng build`
|
|
||||||
- **Run unit tests**: `ng test`
|
|
||||||
|
|
||||||
### Data Hub Connector (NestJS Backend)
|
|
||||||
|
|
||||||
The Data Hub Connector is a NestJS-based backend service responsible for managing data, with database interactions handled by Prisma.
|
|
||||||
|
|
||||||
- **Location**: `service/data-hub/data-connector`
|
|
||||||
- **Key Files**:
|
|
||||||
- `nest-cli.json`: NestJS project configuration.
|
|
||||||
- `package.json`: Project dependencies and scripts.
|
|
||||||
- `prisma/schema.prisma`: Database schema definition.
|
|
||||||
|
|
||||||
#### Commands
|
|
||||||
|
|
||||||
- **Install dependencies**: `npm install`
|
|
||||||
- **Run in development mode**: `npm run start:dev`
|
|
||||||
- **Run in production mode**: `npm run start:prod`
|
|
||||||
- **Run tests**: `npm run test`
|
|
||||||
|
|
||||||
### Synapse (Go Plattforms)
|
|
||||||
|
|
||||||
Synapse is a data transformation service written in Go, responsible for processing and integrating data from various sources.
|
|
||||||
|
|
||||||
- **Location**: `plattform/synapse`
|
|
||||||
- **Key Files**:
|
|
||||||
- `go.mod`: Go module definition.
|
|
||||||
- `cmd/synapse/main.go`: Main application entry point.
|
|
||||||
|
|
||||||
#### Commands
|
|
||||||
|
|
||||||
- **Run the application**: `go run ./cmd/synapse`
|
|
||||||
- **Build the application**: `go build -o synapse ./cmd/synapse`
|
|
||||||
|
|
||||||
## Infrastructure
|
|
||||||
|
|
||||||
The infrastructure for the Avicenna project is managed using Pulumi and Ansible, ensuring a reproducible and automated deployment process.
|
|
||||||
|
|
||||||
- **Location**: `infrastructure`
|
|
||||||
- **Key Components**:
|
|
||||||
- **Pulumi**: Used for provisioning cloud resources.
|
|
||||||
- **Ansible**: Used for configuring services like Traefik, Portainer, and Kestra.
|
|
||||||
|
|
||||||
## Workflows
|
|
||||||
|
|
||||||
The project uses Kestra for orchestrating complex workflows, such as ETL processes and ticket management.
|
|
||||||
|
|
||||||
- **Location**: `service/workflows`
|
|
||||||
- **Key Files**:
|
|
||||||
- `avic-etl.yml`: Defines the ETL workflow.
|
|
||||||
- `avic-tickets.yml`: Defines the ticket management workflow.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
To work on the Avicenna project, follow the instructions in the respective service directories. Ensure you have the required runtimes and dependencies installed, such as Node.js, Go, and the Angular CLI.
|
|
||||||
2
TODO.md
2
TODO.md
|
|
@ -1,2 +0,0 @@
|
||||||
- [ ] Anomalie "Anwesenheit vor Arbeitszeit" abschliessen
|
|
||||||
- [ ] Add Dyflexis User Access Endpoints to ETL
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
/bin/
|
||||||
|
/node_modules/
|
||||||
|
.local/
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
- name: Nodes initialisieren und härten
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: common
|
|
||||||
tags: common
|
|
||||||
- role: ssh_hardening
|
|
||||||
tags: ssh
|
|
||||||
- role: ufw_firewall
|
|
||||||
tags: firewall
|
|
||||||
- role: fail2ban
|
|
||||||
tags: fail2ban
|
|
||||||
handlers:
|
|
||||||
- name: restart sshd
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: ssh
|
|
||||||
state: restarted
|
|
||||||
- name: restart fail2ban
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: fail2ban
|
|
||||||
state: restarted
|
|
||||||
- name: Setup Ceph Cluster and CephFS
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
roles:
|
|
||||||
- role: ceph_setup
|
|
||||||
|
|
||||||
- name: Docker Swarm initialisieren
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
roles:
|
|
||||||
- role: docker_swarm
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
- name: Infrastruktur Dienste bereitstellen
|
|
||||||
hosts: all
|
|
||||||
gather_facts: true
|
|
||||||
roles:
|
|
||||||
# - traefik
|
|
||||||
# - portainer
|
|
||||||
# - hyperdx
|
|
||||||
- kestra
|
|
||||||
# - authentik
|
|
||||||
# # - dockge
|
|
||||||
# - signoz
|
|
||||||
# - signoz-infra
|
|
||||||
# - leantime
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
APT::Periodic::Update-Package-Lists "1";
|
|
||||||
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
||||||
APT::Periodic::AutocleanInterval "7";
|
|
||||||
APT::Periodic::Unattended-Upgrade "1";
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
Unattended-Upgrade::Allowed-Origins {
|
|
||||||
"${distro_id}:${distro_codename}-security";
|
|
||||||
};
|
|
||||||
Unattended-Upgrade::Package-Blacklist {
|
|
||||||
};
|
|
||||||
Unattended-Upgrade::DevRelease "false";
|
|
||||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
|
||||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
||||||
Unattended-Upgrade::Automatic-Reboot "false";
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
[client.admin]
|
|
||||||
key = AQDfUDBpnhHKNBAAwIRER17JF8flD9GSeNtFlg==
|
|
||||||
caps mds = "allow *"
|
|
||||||
caps mgr = "allow *"
|
|
||||||
caps mon = "allow *"
|
|
||||||
caps osd = "allow *"
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# minimal ceph.conf for f103f9ee-d058-11f0-aee8-920006d45637
|
|
||||||
[global]
|
|
||||||
fsid = f103f9ee-d058-11f0-aee8-920006d45637
|
|
||||||
mon_host = [v2:10.0.1.2:3300/0,v1:10.0.1.2:6789/0]
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
admin_user: 'admin'
|
|
||||||
# Passwords are generated by this command: ansible all -i localhost, -m debug -a 'msg={{ "" | password_hash("sha512", "") }}'
|
|
||||||
admin_password: '$6$rounds=656000$C43bVkou378rvtx8$J1KJZ6uw/KnLGc7nFSixAdsNDKUaSw.SGA38ZtZHwHB8eB5a/rRm1qJ8QR/wIxxylLJ0i7ByaW9CAjpbDYaQH/' # 7V#4kW9RvJfmxAnJV8zWbLrp%%SXRJ#kvGkowR98TY # salt=C43bVkou378rvtx8
|
|
||||||
cephadm_password: '$6$rounds=656000$nE9mLm8AbEshiHr7$9HkqmR2Fr.0gKXwzml7CPhaWY9Ul4t0hdHiFldAI8ShISp6yB0NvehbTDnCKnfBO5UTL591IsNKda54WMa6MS.' # fM5$kZP4d!%$ZocBkhj85q^2GuwwN35YHxS!neC3AM # salt=nE9mLm8AbEshiHr7
|
|
||||||
ssh_port: 22
|
|
||||||
cephfs_name: "shared-fs"
|
|
||||||
ceph_osd_device: "/dev/sdb"
|
|
||||||
public_interface: 'eth0'
|
|
||||||
private_interface: 'enp7s0'
|
|
||||||
authorized_keys:
|
|
||||||
- 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtYTptTN9ggoy0aUKXmxaPKpclEn86jM7s5UtTw1JJI'
|
|
||||||
hetzner_api_key: "wqwAS4LINKOxgkyeECrHFOq1MRiOhFe236UyNBjs7fIhmOcKrXCmuncVPyt0S7Ac"
|
|
||||||
|
|
||||||
ceph_volume: /mnt/cephfs
|
|
||||||
main_domain: avicenna.hamburg
|
|
||||||
traefik_public_net: traefik_public
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[all:children]
|
|
||||||
managers
|
|
||||||
workers
|
|
||||||
|
|
||||||
[managers]
|
|
||||||
manager-node-1 ansible_host=188.245.52.124 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key
|
|
||||||
manager-node-2 ansible_host=116.202.18.42 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key
|
|
||||||
manager-node-3 ansible_host=167.235.227.234 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key
|
|
||||||
|
|
||||||
[workers]
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
---
|
|
||||||
# - name: Nodes initialisieren und härten
|
|
||||||
# hosts: all
|
|
||||||
# become: true
|
|
||||||
|
|
||||||
# roles:
|
|
||||||
# - role: common
|
|
||||||
# tags: common
|
|
||||||
# - role: ssh_hardening
|
|
||||||
# tags: ssh
|
|
||||||
# - role: ufw_firewall
|
|
||||||
# tags: firewall
|
|
||||||
# - role: fail2ban
|
|
||||||
# tags: fail2ban
|
|
||||||
# handlers:
|
|
||||||
# - name: restart sshd
|
|
||||||
# ansible.builtin.service:
|
|
||||||
# name: ssh
|
|
||||||
# state: restarted
|
|
||||||
# - name: restart fail2ban
|
|
||||||
# ansible.builtin.service:
|
|
||||||
# name: fail2ban
|
|
||||||
# state: restarted
|
|
||||||
# - name: Setup Ceph Cluster and CephFS
|
|
||||||
# hosts: all
|
|
||||||
# become: true
|
|
||||||
# roles:
|
|
||||||
# - role: ceph_setup
|
|
||||||
|
|
||||||
# - name: Docker Swarm initialisieren
|
|
||||||
# hosts: all
|
|
||||||
# become: true
|
|
||||||
# roles:
|
|
||||||
# - role: docker_swarm
|
|
||||||
|
|
||||||
- name: Infrastruktur Dienste bereitstellen
|
|
||||||
hosts: all
|
|
||||||
gather_facts: true
|
|
||||||
roles:
|
|
||||||
- traefik
|
|
||||||
# - authentik
|
|
||||||
# # - dockge
|
|
||||||
- portainer
|
|
||||||
# - signoz
|
|
||||||
# - signoz-infra
|
|
||||||
# - leantime
|
|
||||||
- kestra
|
|
||||||
# - hyperdx
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
---
|
|
||||||
- name: CEPH | Private IP des ersten Managers ermitteln
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
ceph_bootstrap_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}"
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: CEPH | Cluster auf dem ersten Manager initialisieren (Bootstrap)
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "cephadm bootstrap --mon-ip {{ ceph_bootstrap_ip }}"
|
|
||||||
creates: /etc/ceph/ceph.conf
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: CEPH | Öffentlichen SSH-Schlüssel von cephadm abrufen
|
|
||||||
ansible.builtin.command: "cephadm shell -- ceph cephadm get-pub-key"
|
|
||||||
register: cephadm_pub_key
|
|
||||||
changed_when: false
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | Öffentlichen Schlüssel von cephadm auf allen Knoten für root verteilen
|
|
||||||
ansible.posix.authorized_key:
|
|
||||||
user: root
|
|
||||||
key: "{{ hostvars[groups['managers'][0]]['cephadm_pub_key'].stdout }}"
|
|
||||||
state: present
|
|
||||||
key_options: 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty'
|
|
||||||
|
|
||||||
- name: CEPH | Andere Knoten zum Ceph-Cluster hinzufügen
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "ceph orch host add {{ item }} {{ hostvars[item]['ansible_' + private_interface]['ipv4']['address'] }}"
|
|
||||||
loop: "{{ groups['all'] }}"
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | Prüfen, ob bereits OSDs (Speichergeräte) vorhanden sind
|
|
||||||
ansible.builtin.command: "ceph osd ls"
|
|
||||||
register: existing_osds
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | Spezifische Festplatte ({{ ceph_osd_device }}) auf jedem Knoten als OSD hinzufügen
|
|
||||||
ansible.builtin.command: "ceph orch daemon add osd {{ item }}:{{ ceph_osd_device }}"
|
|
||||||
loop: "{{ groups['all'] }}"
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
when: existing_osds.stdout | length == 0
|
|
||||||
|
|
||||||
- name: CEPH | Prüfen, ob CephFS bereits existiert
|
|
||||||
ansible.builtin.command: "ceph fs ls -f json"
|
|
||||||
register: cephfs_list
|
|
||||||
changed_when: false
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | CephFS Pools und Dateisystem erstellen, falls nicht vorhanden
|
|
||||||
block:
|
|
||||||
- name: Metadaten-Pool für CephFS erstellen
|
|
||||||
ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_metadata"
|
|
||||||
- name: Daten-Pool für CephFS erstellen
|
|
||||||
ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_data"
|
|
||||||
- name: CephFS-Dateisystem erstellen
|
|
||||||
ansible.builtin.command: "ceph fs new {{ cephfs_name }} {{ cephfs_name }}_metadata {{ cephfs_name }}_data"
|
|
||||||
when: cephfs_list.stdout | from_json | length == 0
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | Metadaten-Server (MDS) für CephFS starten
|
|
||||||
ansible.builtin.command: "ceph orch apply mds {{ cephfs_name }} --placement=2"
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
when: cephfs_list.stdout | from_json | length == 0
|
|
||||||
|
|
||||||
- name: CEPH | Ceph Admin-Schlüssel für das Mounten abrufen
|
|
||||||
ansible.builtin.command: "ceph auth get-key client.admin"
|
|
||||||
register: ceph_admin_key
|
|
||||||
changed_when: false
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: CEPH | Mount-Punkt für CephFS erstellen
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/cephfs
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: CEPH | CephFS auf allen Knoten mounten (und in /etc/fstab eintragen)
|
|
||||||
ansible.posix.mount:
|
|
||||||
path: /mnt/cephfs
|
|
||||||
src: "{{ hostvars[groups['managers'][0]]['ceph_bootstrap_ip'] }}:/"
|
|
||||||
fstype: ceph
|
|
||||||
opts: "name=admin,secret={{ ceph_admin_key.stdout }}"
|
|
||||||
state: mounted
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
---
|
|
||||||
- name: COMMON | Systempakete aktualisieren und upgraden
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: true
|
|
||||||
upgrade: dist
|
|
||||||
autoremove: true
|
|
||||||
autoclean: true
|
|
||||||
|
|
||||||
- name: COMMON | Notwendige Pakete installieren
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- ufw
|
|
||||||
- fail2ban
|
|
||||||
- unattended-upgrades
|
|
||||||
- apt-listchanges
|
|
||||||
- docker-ce
|
|
||||||
- python3-pip
|
|
||||||
- chrony
|
|
||||||
- lvm2
|
|
||||||
- cephadm
|
|
||||||
- ceph-common
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: COMMON | Chrony Dienst starten und aktivieren
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: chronyd
|
|
||||||
state: started
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
- name: COMMON | Docker Dienst starten und aktivieren
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: docker
|
|
||||||
state: started
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
- name: COMMON | Einen dedizierten Admin-Benutzer erstellen
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ admin_user }}"
|
|
||||||
password: "{{ admin_password}}"
|
|
||||||
shell: /bin/bash
|
|
||||||
groups: sudo,docker
|
|
||||||
append: true
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: COMMON | SSH-Schlüssel für den Admin-Benutzer einrichten
|
|
||||||
ansible.posix.authorized_key:
|
|
||||||
user: "{{ admin_user }}"
|
|
||||||
key: "{{ item }}"
|
|
||||||
state: present
|
|
||||||
with_items: "{{ authorized_keys }}"
|
|
||||||
|
|
||||||
- name: COMMON | Ensure group "cephadm" exists
|
|
||||||
group:
|
|
||||||
name: cephadm
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: COMMON | cephadm-Benutzer erstellen
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "cephadm"
|
|
||||||
password: "{{ cephadm_password }}"
|
|
||||||
shell: /bin/bash
|
|
||||||
groups: sudo,docker
|
|
||||||
append: yes
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: COMMON | .ssh Verzeichnis für cephadm-Benutzer erstellen
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /home/cephadm/.ssh
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: COMMON | Passwortloses Sudo für cephadm-Benutzer erlauben
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: "/etc/sudoers.d/91-cephadm-nopasswd"
|
|
||||||
content: "cephadm ALL=(ALL) NOPASSWD: ALL"
|
|
||||||
mode: '0440'
|
|
||||||
validate: 'visudo -cf %s'
|
|
||||||
|
|
||||||
- name: COMMON | ed25519 SSH-Schlüssel für cephadm-Benutzer generieren (nur auf dem ersten Manager)
|
|
||||||
community.crypto.openssh_keypair:
|
|
||||||
path: /home/cephadm/.ssh/id_ed25519
|
|
||||||
type: ed25519
|
|
||||||
owner: cephadm
|
|
||||||
group: cephadm
|
|
||||||
mode: '0600'
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm abrufen
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /home/cephadm/.ssh/id_ed25519.pub
|
|
||||||
register: cephadm_ssh_pub_key
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm auf allen Knoten verteilen
|
|
||||||
ansible.posix.authorized_key:
|
|
||||||
user: cephadm
|
|
||||||
key: "{{ hostvars[groups['managers'][0]]['cephadm_ssh_pub_key']['content'] | b64decode }}"
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: COMMON | Automatische Sicherheitsupdates konfigurieren
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: assets/50unattended-upgrades
|
|
||||||
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
|
|
||||||
- name: COMMON | Periodische Auto-Updates aktivieren
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: assets/20auto-upgrades
|
|
||||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
---
|
|
||||||
- name: SWARM | Ensure Docker SDK for Python is installed
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: python3-docker
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: SWARM | Get interface IP address for the manager
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
manager_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}"
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: SWARM | Initialize the Docker Swarm
|
|
||||||
community.docker.docker_swarm:
|
|
||||||
state: present
|
|
||||||
advertise_addr: "{{ manager_ip }}"
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
register: swarm_init_result
|
|
||||||
|
|
||||||
- name: SWARM | Get the join tokens
|
|
||||||
community.docker.docker_swarm_info:
|
|
||||||
register: swarm_info
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: SWARM | Verify that join tokens were fetched
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- swarm_info is defined
|
|
||||||
- swarm_info.swarm_facts is defined
|
|
||||||
- swarm_info.swarm_facts.JoinTokens.Manager is defined
|
|
||||||
- swarm_info.swarm_facts.JoinTokens.Worker is defined
|
|
||||||
fail_msg: "Konnte die Join-Tokens vom Swarm Manager nicht abrufen. Ist der Swarm korrekt initialisiert?"
|
|
||||||
success_msg: "Join-Tokens erfolgreich abgerufen."
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: SWARM | Join manager nodes to the Swarm
|
|
||||||
community.docker.docker_swarm:
|
|
||||||
state: join
|
|
||||||
remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ]
|
|
||||||
join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Manager'] }}"
|
|
||||||
when: inventory_hostname in groups['managers']
|
|
||||||
|
|
||||||
- name: SWARM | Join worker nodes to the Swarm
|
|
||||||
community.docker.docker_swarm:
|
|
||||||
state: join
|
|
||||||
remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ]
|
|
||||||
join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Worker'] }}"
|
|
||||||
when: inventory_hostname in groups['workers']
|
|
||||||
|
|
||||||
- name: SWARM | Verify Swarm Cluster State (run on manager)
|
|
||||||
ansible.builtin.command: docker node ls
|
|
||||||
register: swarm_nodes
|
|
||||||
changed_when: false
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
||||||
- name: SWARM | Display cluster state
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "{{ swarm_nodes.stdout_lines }}"
|
|
||||||
when: inventory_hostname == groups['managers'][0]
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
- name: FAIL2BAN | Eine lokale Jail-Konfiguration erstellen
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: jail.local.j2
|
|
||||||
dest: /etc/fail2ban/jail.local
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
notify: restart fail2ban
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
[DEFAULT]
|
|
||||||
bantime = 1h
|
|
||||||
findtime = 10m
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[sshd]
|
|
||||||
enabled = true
|
|
||||||
port = {{ ssh_port }}
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<clickhouse>
|
|
||||||
<logger>
|
|
||||||
<level>debug</level>
|
|
||||||
<console>true</console>
|
|
||||||
<log remove="remove" />
|
|
||||||
<errorlog remove="remove" />
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<listen_host>0.0.0.0</listen_host>
|
|
||||||
<http_port>8123</http_port>
|
|
||||||
<tcp_port>9000</tcp_port>
|
|
||||||
<interserver_http_host>ch-server</interserver_http_host>
|
|
||||||
<interserver_http_port>9009</interserver_http_port>
|
|
||||||
|
|
||||||
<max_connections>4096</max_connections>
|
|
||||||
<keep_alive_timeout>64</keep_alive_timeout>
|
|
||||||
<max_concurrent_queries>100</max_concurrent_queries>
|
|
||||||
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
|
|
||||||
<mark_cache_size>5368709120</mark_cache_size>
|
|
||||||
|
|
||||||
<path>/var/lib/clickhouse/</path>
|
|
||||||
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
|
|
||||||
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
|
|
||||||
|
|
||||||
<user_directories>
|
|
||||||
<users_xml>
|
|
||||||
<path>users.xml</path>
|
|
||||||
</users_xml>
|
|
||||||
</user_directories>
|
|
||||||
<!-- <users_config>users.xml</users_config> -->
|
|
||||||
<default_profile>default</default_profile>
|
|
||||||
<default_database>default</default_database>
|
|
||||||
<timezone>UTC</timezone>
|
|
||||||
<mlock_executable>false</mlock_executable>
|
|
||||||
|
|
||||||
<!-- Prometheus exporter -->
|
|
||||||
<prometheus>
|
|
||||||
<endpoint>/metrics</endpoint>
|
|
||||||
<port>9363</port>
|
|
||||||
<metrics>true</metrics>
|
|
||||||
<events>true</events>
|
|
||||||
<asynchronous_metrics>true</asynchronous_metrics>
|
|
||||||
<errors>true</errors>
|
|
||||||
</prometheus>
|
|
||||||
|
|
||||||
<!-- Query log. Used only for queries with setting log_queries = 1. -->
|
|
||||||
<query_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>query_log</table>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</query_log>
|
|
||||||
|
|
||||||
<!-- Metric log contains rows with current values of ProfileEvents, CurrentMetrics collected
|
|
||||||
with "collect_interval_milliseconds" interval. -->
|
|
||||||
<metric_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>metric_log</table>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
<collect_interval_milliseconds>1000</collect_interval_milliseconds>
|
|
||||||
</metric_log>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Asynchronous metric log contains values of metrics from
|
|
||||||
system.asynchronous_metrics.
|
|
||||||
-->
|
|
||||||
<asynchronous_metric_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>asynchronous_metric_log</table>
|
|
||||||
<!--
|
|
||||||
Asynchronous metrics are updated once a minute, so there is
|
|
||||||
no need to flush more often.
|
|
||||||
-->
|
|
||||||
<flush_interval_milliseconds>7000</flush_interval_milliseconds>
|
|
||||||
</asynchronous_metric_log>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
OpenTelemetry log contains OpenTelemetry trace spans.
|
|
||||||
-->
|
|
||||||
<opentelemetry_span_log>
|
|
||||||
<!--
|
|
||||||
The default table creation code is insufficient, this <engine> spec
|
|
||||||
is a workaround. There is no 'event_time' for this log, but two times,
|
|
||||||
start and finish. It is sorted by finish time, to avoid inserting
|
|
||||||
data too far away in the past (probably we can sometimes insert a span
|
|
||||||
that is seconds earlier than the last span in the table, due to a race
|
|
||||||
between several spans inserted in parallel). This gives the spans a
|
|
||||||
global order that we can use to e.g. retry insertion into some external
|
|
||||||
system.
|
|
||||||
-->
|
|
||||||
<engine>
|
|
||||||
engine MergeTree
|
|
||||||
partition by toYYYYMM(finish_date)
|
|
||||||
order by (finish_date, finish_time_us, trace_id)
|
|
||||||
</engine>
|
|
||||||
<database>system</database>
|
|
||||||
<table>opentelemetry_span_log</table>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</opentelemetry_span_log>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Crash log. Stores stack traces for fatal errors.
|
|
||||||
This table is normally empty. -->
|
|
||||||
<crash_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>crash_log</table>
|
|
||||||
|
|
||||||
<partition_by />
|
|
||||||
<flush_interval_milliseconds>1000</flush_interval_milliseconds>
|
|
||||||
</crash_log>
|
|
||||||
|
|
||||||
<!-- Profiling on Processors level. -->
|
|
||||||
<processors_profile_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>processors_profile_log</table>
|
|
||||||
|
|
||||||
<partition_by>toYYYYMM(event_date)</partition_by>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</processors_profile_log>
|
|
||||||
|
|
||||||
<!-- Uncomment if use part log.
|
|
||||||
Part log contains information about all actions with parts in MergeTree tables (creation, deletion,
|
|
||||||
merges, downloads).-->
|
|
||||||
<part_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>part_log</table>
|
|
||||||
<partition_by>toYYYYMM(event_date)</partition_by>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</part_log>
|
|
||||||
|
|
||||||
<!-- Trace log. Stores stack traces collected by query profilers.
|
|
||||||
See query_profiler_real_time_period_ns and query_profiler_cpu_time_period_ns settings. -->
|
|
||||||
<trace_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>trace_log</table>
|
|
||||||
|
|
||||||
<partition_by>toYYYYMM(event_date)</partition_by>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</trace_log>
|
|
||||||
|
|
||||||
<!-- Query thread log. Has information about all threads participated in query execution.
|
|
||||||
Used only for queries with setting log_query_threads = 1. -->
|
|
||||||
<query_thread_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>query_thread_log</table>
|
|
||||||
<partition_by>toYYYYMM(event_date)</partition_by>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</query_thread_log>
|
|
||||||
|
|
||||||
<!-- Query views log. Has information about all dependent views associated with a query.
|
|
||||||
Used only for queries with setting log_query_views = 1. -->
|
|
||||||
<query_views_log>
|
|
||||||
<database>system</database>
|
|
||||||
<table>query_views_log</table>
|
|
||||||
<partition_by>toYYYYMM(event_date)</partition_by>
|
|
||||||
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
|
|
||||||
</query_views_log>
|
|
||||||
|
|
||||||
<remote_servers>
|
|
||||||
<hdx_cluster>
|
|
||||||
<shard>
|
|
||||||
<replica>
|
|
||||||
<host>ch-server</host>
|
|
||||||
<port>9000</port>
|
|
||||||
</replica>
|
|
||||||
</shard>
|
|
||||||
</hdx_cluster>
|
|
||||||
</remote_servers>
|
|
||||||
|
|
||||||
<distributed_ddl>
|
|
||||||
<path>/clickhouse/task_queue/ddl</path>
|
|
||||||
</distributed_ddl>
|
|
||||||
|
|
||||||
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
|
|
||||||
</clickhouse>
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<clickhouse>
|
|
||||||
<profiles>
|
|
||||||
<default>
|
|
||||||
<max_memory_usage>10000000000</max_memory_usage>
|
|
||||||
<use_uncompressed_cache>0</use_uncompressed_cache>
|
|
||||||
<load_balancing>in_order</load_balancing>
|
|
||||||
<log_queries>1</log_queries>
|
|
||||||
</default>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
<users>
|
|
||||||
<default>
|
|
||||||
<password></password>
|
|
||||||
<profile>default</profile>
|
|
||||||
<networks>
|
|
||||||
<ip>::/0</ip>
|
|
||||||
</networks>
|
|
||||||
<quota>default</quota>
|
|
||||||
</default>
|
|
||||||
<api>
|
|
||||||
<password>api</password>
|
|
||||||
<profile>default</profile>
|
|
||||||
<networks>
|
|
||||||
<ip>::/0</ip>
|
|
||||||
</networks>
|
|
||||||
<quota>default</quota>
|
|
||||||
</api>
|
|
||||||
<worker>
|
|
||||||
<password>worker</password>
|
|
||||||
<profile>default</profile>
|
|
||||||
<networks>
|
|
||||||
<ip>::/0</ip>
|
|
||||||
</networks>
|
|
||||||
<quota>default</quota>
|
|
||||||
</worker>
|
|
||||||
</users>
|
|
||||||
|
|
||||||
<quotas>
|
|
||||||
<default>
|
|
||||||
<interval>
|
|
||||||
<duration>3600</duration>
|
|
||||||
<queries>0</queries>
|
|
||||||
<errors>0</errors>
|
|
||||||
<result_rows>0</result_rows>
|
|
||||||
<read_rows>0</read_rows>
|
|
||||||
<execution_time>0</execution_time>
|
|
||||||
</interval>
|
|
||||||
</default>
|
|
||||||
</quotas>
|
|
||||||
</clickhouse>
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
---
|
|
||||||
- name: HYPERDX | Verzeichnisse erstellen
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "/mnt/cephfs/hyperdx/{{ item.path }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ item.uid }}"
|
|
||||||
group: "{{ item.gid }}"
|
|
||||||
mode: '0755'
|
|
||||||
recurse: yes
|
|
||||||
loop:
|
|
||||||
- { path: 'mongo', uid: 999, gid: 999 } # MongoDB Standard
|
|
||||||
- { path: 'clickhouse/data', uid: 101, gid: 101 } # ClickHouse Standard
|
|
||||||
- { path: 'clickhouse/logs', uid: 101, gid: 101 }
|
|
||||||
- { path: 'clickhouse/config', uid: 101, gid: 101 }
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: HYPERDX | ClickHouse Konfiguration kopieren
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: "{{ item }}"
|
|
||||||
dest: "/mnt/cephfs/hyperdx/clickhouse/config/"
|
|
||||||
owner: 101
|
|
||||||
group: 101
|
|
||||||
mode: '0644'
|
|
||||||
loop:
|
|
||||||
- files/config.xml # Lokal in deinem Ansible Repo
|
|
||||||
- files/users.xml
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: HYPERDX | shared-observability Netzwerk erstellen
|
|
||||||
community.docker.docker_network:
|
|
||||||
name: shared-observability
|
|
||||||
driver: overlay
|
|
||||||
state: present
|
|
||||||
attachable: yes
|
|
||||||
ipam_config:
|
|
||||||
- subnet: '172.16.201.0/24'
|
|
||||||
gateway: '172.16.201.1'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: HYPERDX | Generate Compose file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: docker-compose.yml.j2
|
|
||||||
dest: '{{ data_dir }}/hyperdx.yml'
|
|
||||||
mode: 0644
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: HYPERDX | Deploy stack
|
|
||||||
community.docker.docker_stack:
|
|
||||||
state: present
|
|
||||||
name: hyperdx
|
|
||||||
compose:
|
|
||||||
- '{{ data_dir }}/hyperdx.yml'
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
version: '3.9'
|
|
||||||
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: mongo:5.0.14-focal
|
|
||||||
volumes:
|
|
||||||
- "{{ data_dir }}/mongo:/data/db"
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
# placement:
|
|
||||||
# constraints: [node.role == worker] # DBs besser auf Workern lassen wenn möglich
|
|
||||||
|
|
||||||
otel-collector:
|
|
||||||
image: "clickhouse/clickstack-otel-collector:2"
|
|
||||||
environment:
|
|
||||||
CLICKHOUSE_ENDPOINT: 'tcp://ch-server:9000?dial_timeout=10s'
|
|
||||||
HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE: "default"
|
|
||||||
HYPERDX_LOG_LEVEL: "{{ hyperdx_log_level | default('info') }}"
|
|
||||||
OPAMP_SERVER_URL: 'http://app:{{ hyperdx_opamp_port | default(4320) }}'
|
|
||||||
# Ingestion Ports direkt via Swarm Mesh exponieren für Performance
|
|
||||||
ports:
|
|
||||||
- "4317:4317" # OTLP gRPC
|
|
||||||
- "4318:4318" # OTLP HTTP
|
|
||||||
# - "8888:8888" # Metrics (optional)
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
- shared-observability
|
|
||||||
- traefik_public
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.docker.network=traefik_public"
|
|
||||||
- "traefik.http.routers.otel-collector.rule=Host(`{{ otlp_domain }}`)"
|
|
||||||
- "traefik.http.routers.otel-collector.entrypoints=https"
|
|
||||||
- "traefik.http.routers.otel-collector.tls.certresolver=main"
|
|
||||||
- "traefik.http.services.otel-collector.loadbalancer.server.port=4318"
|
|
||||||
|
|
||||||
app:
|
|
||||||
image: "hyperdx/hyperdx:2"
|
|
||||||
environment:
|
|
||||||
# URLs anpassen für Traefik Erreichbarkeit
|
|
||||||
FRONTEND_URL: "https://{{ hdx_domain }}"
|
|
||||||
HYPERDX_APP_URL: "https://{{ hdx_domain }}"
|
|
||||||
|
|
||||||
HYPERDX_API_KEY: "{{ hyperdx_api_key }}"
|
|
||||||
HYPERDX_API_PORT: "{{ hyperdx_api_port | default(8000) }}"
|
|
||||||
HYPERDX_APP_PORT: "{{ hyperdx_app_port | default(8080) }}"
|
|
||||||
HYPERDX_LOG_LEVEL: "{{ hyperdx_log_level | default('info') }}"
|
|
||||||
MINER_API_URL: 'http://miner:5123' # Falls miner benötigt wird (in original compose nicht definiert?)
|
|
||||||
MONGO_URI: 'mongodb://db:27017/hyperdx'
|
|
||||||
SERVER_URL: "http://127.0.0.1:{{ hyperdx_api_port | default(8000) }}"
|
|
||||||
OPAMP_PORT: "{{ hyperdx_opamp_port | default(4320) }}"
|
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT: 'http://otel-collector:4318'
|
|
||||||
OTEL_SERVICE_NAME: 'hdx-oss-app'
|
|
||||||
USAGE_STATS_ENABLED: "{{ usage_stats_enabled | default('false') }}"
|
|
||||||
# Clickhouse Connection String (Default User/Pass from Clickhouse Image)
|
|
||||||
DEFAULT_CONNECTIONS: >-
|
|
||||||
[{"name":"Local ClickHouse","host":"http://ch-server:8123","username":"default","password":""}]
|
|
||||||
DEFAULT_SOURCES: '{{ hyperdx_default_sources | to_json }}'
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
- traefik_public
|
|
||||||
deploy:
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.docker.network=traefik_public"
|
|
||||||
- "traefik.http.routers.hyperdx.rule=Host(`{{ subdomain }}.{{ main_domain }}`)"
|
|
||||||
- "traefik.http.routers.hyperdx.entrypoints=https"
|
|
||||||
- "traefik.http.routers.hyperdx.tls.certresolver=main"
|
|
||||||
- "traefik.http.services.hyperdx.loadbalancer.server.port={{ hyperdx_app_port | default(8080) }}"
|
|
||||||
|
|
||||||
ch-server:
|
|
||||||
image: clickhouse/clickhouse-server:25.6-alpine
|
|
||||||
environment:
|
|
||||||
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
|
|
||||||
volumes:
|
|
||||||
- "{{ data_dir }}/clickhouse/config/config.xml:/etc/clickhouse-server/config.xml"
|
|
||||||
- "{{ data_dir }}/clickhouse/config/users.xml:/etc/clickhouse-server/users.xml"
|
|
||||||
- "{{ data_dir }}/clickhouse/data:/var/lib/clickhouse"
|
|
||||||
- "{{ data_dir }}/clickhouse/logs:/var/log/clickhouse-server"
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
# placement:
|
|
||||||
# constraints: [node.role == worker]
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
|
|
||||||
networks:
|
|
||||||
internal:
|
|
||||||
driver: overlay
|
|
||||||
traefik_public:
|
|
||||||
external: true
|
|
||||||
shared-observability:
|
|
||||||
external: true
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
data_dir: "{{ ceph_volume }}/hyperdx"
|
|
||||||
subdomain: "hdx"
|
|
||||||
|
|
||||||
hdx_domain: "{{ subdomain }}.{{ main_domain }}"
|
|
||||||
otlp_domain: "otlp.{{ main_domain }}"
|
|
||||||
|
|
||||||
# Generiere einen sicheren Key: `openssl rand -hex 16`
|
|
||||||
hyperdx_api_key: "2bb00477e073d6e2b72e310c39785fa1"
|
|
||||||
hyperdx_api_port: 8000
|
|
||||||
hyperdx_app_port: 8080
|
|
||||||
hyperdx_log_level: "info"
|
|
||||||
hyperdx_opamp_port: 4320
|
|
||||||
|
|
||||||
usage_stats_enabled: "false"
|
|
||||||
|
|
||||||
# vars/main.yml
|
|
||||||
|
|
||||||
# ... (deine anderen Variablen) ...
|
|
||||||
|
|
||||||
# Definition der Datenquellen für das Frontend
|
|
||||||
hyperdx_default_sources:
|
|
||||||
- name: "Logs"
|
|
||||||
kind: "log"
|
|
||||||
from:
|
|
||||||
databaseName: "default"
|
|
||||||
tableName: "otel_logs"
|
|
||||||
timestampValueExpression: "TimestampTime"
|
|
||||||
displayedTimestampValueExpression: "Timestamp"
|
|
||||||
implicitColumnExpression: "Body"
|
|
||||||
serviceNameExpression: "ServiceName"
|
|
||||||
bodyExpression: "Body"
|
|
||||||
eventAttributesExpression: "LogAttributes"
|
|
||||||
resourceAttributesExpression: "ResourceAttributes"
|
|
||||||
defaultTableSelectExpression: "Timestamp,ServiceName,SeverityText,Body"
|
|
||||||
severityTextExpression: "SeverityText"
|
|
||||||
traceIdExpression: "TraceId"
|
|
||||||
spanIdExpression: "SpanId"
|
|
||||||
connection: "Local ClickHouse"
|
|
||||||
traceSourceId: "Traces"
|
|
||||||
sessionSourceId: "Sessions"
|
|
||||||
metricSourceId: "Metrics"
|
|
||||||
|
|
||||||
- name: "Traces"
|
|
||||||
kind: "trace"
|
|
||||||
from:
|
|
||||||
databaseName: "default"
|
|
||||||
tableName: "otel_traces"
|
|
||||||
timestampValueExpression: "Timestamp"
|
|
||||||
displayedTimestampValueExpression: "Timestamp"
|
|
||||||
implicitColumnExpression: "SpanName"
|
|
||||||
serviceNameExpression: "ServiceName"
|
|
||||||
bodyExpression: "SpanName"
|
|
||||||
eventAttributesExpression: "SpanAttributes"
|
|
||||||
resourceAttributesExpression: "ResourceAttributes"
|
|
||||||
defaultTableSelectExpression: "Timestamp,ServiceName,StatusCode,round(Duration/1e6),SpanName"
|
|
||||||
traceIdExpression: "TraceId"
|
|
||||||
spanIdExpression: "SpanId"
|
|
||||||
durationExpression: "Duration"
|
|
||||||
durationPrecision: 9
|
|
||||||
parentSpanIdExpression: "ParentSpanId"
|
|
||||||
spanNameExpression: "SpanName"
|
|
||||||
spanKindExpression: "SpanKind"
|
|
||||||
statusCodeExpression: "StatusCode"
|
|
||||||
statusMessageExpression: "StatusMessage"
|
|
||||||
connection: "Local ClickHouse"
|
|
||||||
logSourceId: "Logs"
|
|
||||||
sessionSourceId: "Sessions"
|
|
||||||
metricSourceId: "Metrics"
|
|
||||||
|
|
||||||
- name: "Metrics"
|
|
||||||
kind: "metric"
|
|
||||||
from:
|
|
||||||
databaseName: "default"
|
|
||||||
tableName: ""
|
|
||||||
timestampValueExpression: "TimeUnix"
|
|
||||||
resourceAttributesExpression: "ResourceAttributes"
|
|
||||||
metricTables:
|
|
||||||
gauge: "otel_metrics_gauge"
|
|
||||||
histogram: "otel_metrics_histogram"
|
|
||||||
sum: "otel_metrics_sum"
|
|
||||||
_id: "682586a8b1f81924e628e808"
|
|
||||||
id: "682586a8b1f81924e628e808"
|
|
||||||
connection: "Local ClickHouse"
|
|
||||||
logSourceId: "Logs"
|
|
||||||
traceSourceId: "Traces"
|
|
||||||
sessionSourceId: "Sessions"
|
|
||||||
|
|
||||||
- name: "Sessions"
|
|
||||||
kind: "session"
|
|
||||||
from:
|
|
||||||
databaseName: "default"
|
|
||||||
tableName: "hyperdx_sessions"
|
|
||||||
timestampValueExpression: "TimestampTime"
|
|
||||||
displayedTimestampValueExpression: "Timestamp"
|
|
||||||
implicitColumnExpression: "Body"
|
|
||||||
serviceNameExpression: "ServiceName"
|
|
||||||
bodyExpression: "Body"
|
|
||||||
eventAttributesExpression: "LogAttributes"
|
|
||||||
resourceAttributesExpression: "ResourceAttributes"
|
|
||||||
defaultTableSelectExpression: "Timestamp,ServiceName,SeverityText,Body"
|
|
||||||
severityTextExpression: "SeverityText"
|
|
||||||
traceIdExpression: "TraceId"
|
|
||||||
spanIdExpression: "SpanId"
|
|
||||||
connection: "Local ClickHouse"
|
|
||||||
logSourceId: "Logs"
|
|
||||||
traceSourceId: "Traces"
|
|
||||||
metricSourceId: "Metrics"
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
---
|
|
||||||
- name: KESTRA | Ensure data directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: '{{ data_dir }}/data/data'
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: KESTRA | Ensure db directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: '{{ data_dir }}/data/db'
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: KESTRA | Konfigurationsdatei für tmpfiles.d erstellen
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "d /tmp/kestra-wd 0755 root root -"
|
|
||||||
dest: /etc/tmpfiles.d/kestra-wd.conf
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
|
|
||||||
- name: KESTRA | Create Kestra working directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /tmp/kestra-wd
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: KESTRA | Generate Compose file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: docker-compose.yml.j2
|
|
||||||
dest: '{{ data_dir }}/kestra.yml'
|
|
||||||
mode: 0644
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: KESTRA | Deploy stack
|
|
||||||
community.docker.docker_stack:
|
|
||||||
state: present
|
|
||||||
name: kestra
|
|
||||||
compose:
|
|
||||||
- /mnt/cephfs/kestra/kestra.yml
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
networks:
|
|
||||||
internal:
|
|
||||||
{{ traefik_public_net }}:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:17
|
|
||||||
volumes:
|
|
||||||
- {{ data_dir }}/data/db:/var/lib/postgresql/data
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: kestra
|
|
||||||
POSTGRES_USER: kestra
|
|
||||||
POSTGRES_PASSWORD: k3str4
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 10
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
|
|
||||||
kestra:
|
|
||||||
image: kestra/kestra:v0.24.2
|
|
||||||
entrypoint: /bin/bash
|
|
||||||
# Note that this is meant for development only. Refer to the documentation for production deployments of Kestra which runs without a root user.
|
|
||||||
user: "root"
|
|
||||||
command:
|
|
||||||
- -c
|
|
||||||
- /app/kestra server standalone --worker-thread=128
|
|
||||||
volumes:
|
|
||||||
- {{ data_dir }}/data/data:/app/storage
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- /tmp/kestra-wd:/tmp/kestra-wd
|
|
||||||
environment:
|
|
||||||
KESTRA_CONFIGURATION: |
|
|
||||||
datasources:
|
|
||||||
postgres:
|
|
||||||
url: jdbc:postgresql://postgres:5432/kestra
|
|
||||||
driverClassName: org.postgresql.Driver
|
|
||||||
username: kestra
|
|
||||||
password: k3str4
|
|
||||||
kestra:
|
|
||||||
tutorialFlows:
|
|
||||||
enabled: false
|
|
||||||
traces:
|
|
||||||
root: DEFAULT
|
|
||||||
micronaut:
|
|
||||||
metrics:
|
|
||||||
export:
|
|
||||||
otlp:
|
|
||||||
enabled: true
|
|
||||||
url: http://signoz_otel-collector:4318/v1/metrics
|
|
||||||
otel:
|
|
||||||
traces:
|
|
||||||
exporter: otlp
|
|
||||||
exporter:
|
|
||||||
otlp:
|
|
||||||
endpoint: http://signoz_otel-collector:4318
|
|
||||||
server:
|
|
||||||
basic-auth:
|
|
||||||
username: ma@coachhamburg.com
|
|
||||||
password: "igyozi9B87yTeiQ6z2sbe8Y4aQLJV58jdaCNu"
|
|
||||||
repository:
|
|
||||||
type: postgres
|
|
||||||
storage:
|
|
||||||
type: local
|
|
||||||
local:
|
|
||||||
base-path: "/app/storage"
|
|
||||||
queue:
|
|
||||||
type: postgres
|
|
||||||
tasks:
|
|
||||||
tmp-dir:
|
|
||||||
path: /tmp/kestra-wd/tmp
|
|
||||||
url: http://localhost:8080/
|
|
||||||
networks:
|
|
||||||
- {{ traefik_public_net }}
|
|
||||||
- internal
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.swarm.network={{ traefik_public_net }}"
|
|
||||||
- "traefik.http.routers.kestra.rule=Host(`{{ subdomain }}.{{ main_domain }}`)"
|
|
||||||
- "traefik.http.routers.kestra.entrypoints=https"
|
|
||||||
- "traefik.http.routers.kestra.tls=true"
|
|
||||||
- "traefik.http.routers.kestra.tls.certresolver=main"
|
|
||||||
- "traefik.http.services.kestra.loadbalancer.server.port=8080"
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
subdomain: workflow
|
|
||||||
data_dir: "{{ ceph_volume }}/kestra"
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
---
|
|
||||||
- name: PORTAINER | Ensure data directories
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: '{{ data_dir }}/data'
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: PORTAINER | Generate Compose file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: docker-compose.yml.j2
|
|
||||||
dest: '{{ data_dir }}/portainer.yml'
|
|
||||||
mode: 0644
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: PORTAINER | Deploy stack
|
|
||||||
community.docker.docker_stack:
|
|
||||||
state: present
|
|
||||||
name: portainer
|
|
||||||
compose:
|
|
||||||
- '{{ data_dir }}/portainer.yml'
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
version: '3.2'
|
|
||||||
|
|
||||||
services:
|
|
||||||
agent:
|
|
||||||
image: portainer/agent:2.33.5
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
|
||||||
networks:
|
|
||||||
- {{ traefik_public_net }}
|
|
||||||
deploy:
|
|
||||||
mode: global
|
|
||||||
placement:
|
|
||||||
constraints: [node.platform.os == linux]
|
|
||||||
|
|
||||||
portainer:
|
|
||||||
image: portainer/portainer-ce:2.33.5
|
|
||||||
command: -H tcp://portainer_agent:9001 --tlsskipverify
|
|
||||||
volumes:
|
|
||||||
- {{ data_dir }}/data:/data
|
|
||||||
networks:
|
|
||||||
- {{ traefik_public_net }}
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
replicas: 1
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.swarm.network={{ traefik_public_net }}"
|
|
||||||
- "traefik.http.routers.portainer.rule=Host(`{{ subdomain }}.{{ main_domain }}`)"
|
|
||||||
- "traefik.http.routers.portainer.entrypoints=https"
|
|
||||||
- "traefik.http.routers.portainer.tls=true"
|
|
||||||
- "traefik.http.routers.portainer.tls.certresolver=main"
|
|
||||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
|
||||||
placement:
|
|
||||||
constraints: [node.hostname == manager-node-3]
|
|
||||||
|
|
||||||
networks:
|
|
||||||
{{ traefik_public_net }}:
|
|
||||||
external: true
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
subdomain: port
|
|
||||||
data_dir: "{{ ceph_volume }}/portainer"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
---
|
|
||||||
- name: SSH | Ensure privilege separation directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /run/sshd
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: SSH | Root-Login nur mit Schlüssel erlauben
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/ssh/sshd_config
|
|
||||||
regexp: '^#?PermitRootLogin'
|
|
||||||
line: 'PermitRootLogin prohibit-password'
|
|
||||||
validate: 'sshd -t -f %s'
|
|
||||||
notify: restart sshd
|
|
||||||
|
|
||||||
- name: SSH | Passwort-Authentifizierung deaktivieren
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/ssh/sshd_config
|
|
||||||
regexp: '^#?PasswordAuthentication'
|
|
||||||
line: 'PasswordAuthentication no'
|
|
||||||
validate: 'sshd -t -f %s'
|
|
||||||
notify: restart sshd
|
|
||||||
|
|
||||||
- name: SSH | Leere Passwörter verbieten
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/ssh/sshd_config
|
|
||||||
regexp: '^#?PermitEmptyPasswords'
|
|
||||||
line: 'PermitEmptyPasswords no'
|
|
||||||
validate: 'sshd -t -f %s'
|
|
||||||
notify: restart sshd
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
[http]
|
|
||||||
[http.middlewares]
|
|
||||||
[http.middlewares.authentik.forwardAuth]
|
|
||||||
address = "http://authentik_server:9000/outpost.goauthentik.io/auth/traefik"
|
|
||||||
trustForwardHeader = true
|
|
||||||
authResponseHeaders = [
|
|
||||||
"X-authentik-username",
|
|
||||||
"X-authentik-groups",
|
|
||||||
"X-authentik-email",
|
|
||||||
"X-authentik-name",
|
|
||||||
"X-authentik-uid",
|
|
||||||
"X-authentik-jwt",
|
|
||||||
"X-authentik-meta-jwks",
|
|
||||||
"X-authentik-meta-outpost",
|
|
||||||
"X-authentik-meta-provider",
|
|
||||||
"X-authentik-meta-app",
|
|
||||||
"X-authentik-meta-version"
|
|
||||||
]
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
[global]
|
|
||||||
checkNewVersion = true
|
|
||||||
sendAnonymousUsage = false
|
|
||||||
|
|
||||||
[experimental]
|
|
||||||
otlpLogs = true
|
|
||||||
|
|
||||||
[core]
|
|
||||||
defaultRuleSyntax = "v2"
|
|
||||||
|
|
||||||
[accessLog]
|
|
||||||
filePath = "/logs/access.log"
|
|
||||||
format = "json"
|
|
||||||
|
|
||||||
# Enable the Dashboard
|
|
||||||
[api]
|
|
||||||
dashboard = true
|
|
||||||
|
|
||||||
# Write out Traefik logs
|
|
||||||
[log]
|
|
||||||
level = "INFO"
|
|
||||||
format = "json"
|
|
||||||
filePath = "/logs/traefik.log"
|
|
||||||
# [log.otlp.http]
|
|
||||||
# endpoint = "http://signoz_otel-collector:4318/v1/logs"
|
|
||||||
|
|
||||||
[entryPoints]
|
|
||||||
[entryPoints.http]
|
|
||||||
address = ":80"
|
|
||||||
[entryPoints.http.http.redirections.entryPoint]
|
|
||||||
to = "https"
|
|
||||||
scheme = "https"
|
|
||||||
|
|
||||||
[entryPoints.https]
|
|
||||||
address = ":443"
|
|
||||||
# [entryPoints.https.http.tls]
|
|
||||||
# certResolver = "main"
|
|
||||||
|
|
||||||
# OTel
|
|
||||||
# [tracing]
|
|
||||||
# serviceName = "traefik"
|
|
||||||
# [tracing.otlp.http]
|
|
||||||
# endpoint = "http://signoz_otel-collector:4318/v1/traces"
|
|
||||||
# [tracing.otlp.http.tls]
|
|
||||||
# insecureSkipVerify = true
|
|
||||||
|
|
||||||
# # Metrics
|
|
||||||
# [metrics]
|
|
||||||
# addInternals = false
|
|
||||||
# [metrics.otlp]
|
|
||||||
# serviceName = "traefik"
|
|
||||||
# addEntryPointsLabels = true
|
|
||||||
# addRoutersLabels = true
|
|
||||||
# addServicesLabels = true
|
|
||||||
# [metrics.otlp.http]
|
|
||||||
# endpoint = "http://signoz_otel-collector:4318/v1/metrics"
|
|
||||||
# [metrics.otlp.grpc]
|
|
||||||
# endpoint = "monitoring_alloy:4317"
|
|
||||||
# insecure = true
|
|
||||||
|
|
||||||
# Let's Encrypt
|
|
||||||
[certificatesResolvers.main.acme]
|
|
||||||
email = "ma@coachhamburg.com"
|
|
||||||
storage = "acme.json"
|
|
||||||
# uncomment to use staging CA for testing
|
|
||||||
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
||||||
# [certificatesResolvers.main.acme.tlsChallenge]
|
|
||||||
[certificatesResolvers.main.acme.dnsChallenge]
|
|
||||||
provider = "hetzner"
|
|
||||||
# Uncomment to use HTTP validation, like a caveman!
|
|
||||||
# [certificatesResolvers.main.acme.httpChallenge]
|
|
||||||
# entryPoint = "http"
|
|
||||||
|
|
||||||
[providers]
|
|
||||||
[providers.swarm]
|
|
||||||
endpoint = "unix:///var/run/docker.sock"
|
|
||||||
exposedByDefault = false
|
|
||||||
[providers.file]
|
|
||||||
directory = "/etc/traefik/dynamic"
|
|
||||||
watch = true
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
---
|
|
||||||
- name: TRAEFIK | Copy Stack Files
|
|
||||||
copy:
|
|
||||||
directory_mode: true
|
|
||||||
src: traefik
|
|
||||||
dest: "{{ ceph_volume }}"
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: TRAEFIK | Generate Compose file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: docker-compose.yml.j2
|
|
||||||
dest: "{{ data_dir }}/traefik.yml"
|
|
||||||
mode: 0644
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: TRAEFIK | Ensure permissions on acme.json
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ data_dir }}/data/acme.json"
|
|
||||||
mode: '0600'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: TRAEFIK | traefik_public Netzwerk erstellen
|
|
||||||
community.docker.docker_network:
|
|
||||||
name: traefik_public
|
|
||||||
driver: overlay
|
|
||||||
state: present
|
|
||||||
attachable: yes
|
|
||||||
ipam_config:
|
|
||||||
- subnet: '172.16.200.0/24'
|
|
||||||
gateway: '172.16.200.1'
|
|
||||||
run_once: true
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
|
|
||||||
- name: TRAEFIK | Deploy app stack
|
|
||||||
community.docker.docker_stack:
|
|
||||||
state: present
|
|
||||||
name: traefik
|
|
||||||
compose:
|
|
||||||
- "{{ data_dir }}/traefik.yml"
|
|
||||||
delegate_to: "{{ groups['managers'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: traefik:v3.6.2
|
|
||||||
ports:
|
|
||||||
- target: 80
|
|
||||||
published: 80
|
|
||||||
protocol: tcp
|
|
||||||
mode: host
|
|
||||||
- target: 443
|
|
||||||
published: 443
|
|
||||||
protocol: tcp
|
|
||||||
mode: host
|
|
||||||
- target: 8080
|
|
||||||
published: 8080
|
|
||||||
protocol: tcp
|
|
||||||
environment:
|
|
||||||
- HETZNER_API_TOKEN={{ hetzner_api_key }}
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- {{ data_dir }}/config:/etc/traefik
|
|
||||||
- {{ data_dir }}/data/logs:/logs
|
|
||||||
- {{ data_dir }}/data/acme.json:/acme.json
|
|
||||||
# healthcheck:
|
|
||||||
# test: ["CMD", "traefik", "healthcheck", "--ping"]
|
|
||||||
# timeout: 1s
|
|
||||||
# interval: 10s
|
|
||||||
# retries: 3
|
|
||||||
# start_period: 10s
|
|
||||||
networks:
|
|
||||||
- {{ traefik_public_net }}
|
|
||||||
# Global mode makes an instance of traefik listen on _every_ node, so that regardless of which
|
|
||||||
# node the request arrives on, it'll be forwarded to the correct backend service.
|
|
||||||
deploy:
|
|
||||||
mode: global
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.swarm.network={{ traefik_public_net }}"
|
|
||||||
- "traefik.http.routers.api.rule=Host(`{{ subdomain }}.{{ main_domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
|
||||||
- "traefik.http.routers.api.entrypoints=https"
|
|
||||||
{% if use_authentik %} - "traefik.http.routers.api.middlewares=authentik@file"{% endif %}
|
|
||||||
# - "traefik.http.routers.api.tls.domains[0].main={{ main_domain }}"
|
|
||||||
# - "traefik.http.routers.api.tls.domains[0].sans=*.{{ main_domain }}"
|
|
||||||
- "traefik.http.routers.api.tls=true"
|
|
||||||
- "traefik.http.routers.api.tls.certresolver=main"
|
|
||||||
- "traefik.http.routers.api.service=api@internal"
|
|
||||||
- "traefik.http.services.dummy.loadbalancer.server.port=9999"
|
|
||||||
placement:
|
|
||||||
constraints: [node.role == manager]
|
|
||||||
|
|
||||||
networks:
|
|
||||||
{{ traefik_public_net }}:
|
|
||||||
external: true
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
subdomain: router
|
|
||||||
use_authentik: false
|
|
||||||
data_dir: "{{ ceph_volume }}/traefik"
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
---
|
|
||||||
- name: FIREWALL | UFW auf Standardeinstellungen zurücksetzen
|
|
||||||
community.general.ufw:
|
|
||||||
state: reset
|
|
||||||
|
|
||||||
- name: FIREWALL | Standardmäßig allen ausgehenden Traffic erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
direction: outgoing
|
|
||||||
policy: allow
|
|
||||||
|
|
||||||
- name: FIREWALL | Standardmäßig allen eingehenden Traffic blockieren
|
|
||||||
community.general.ufw:
|
|
||||||
direction: incoming
|
|
||||||
policy: deny
|
|
||||||
|
|
||||||
- name: FIREWALL | Eingehenden SSH-Traffic auf öffentlichem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ ssh_port }}"
|
|
||||||
proto: tcp
|
|
||||||
interface: "{{ public_interface }}"
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: FIREWALL | Eingehenden SSH-Traffic auf privatem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ ssh_port }}"
|
|
||||||
proto: tcp
|
|
||||||
interface: "{{ private_interface }}"
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: FIREWALL | Eingehenden HTTP/HTTPS-Traffic auf öffentlichem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ item.port }}"
|
|
||||||
proto: "{{ item.proto }}"
|
|
||||||
interface: "{{ public_interface }}"
|
|
||||||
direction: in
|
|
||||||
with_items:
|
|
||||||
- { port: '80', proto: 'tcp' }
|
|
||||||
- { port: '443', proto: 'tcp' }
|
|
||||||
|
|
||||||
- name: FIREWALL | Ceph Monitor Ports auf privatem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ item }}"
|
|
||||||
proto: tcp
|
|
||||||
interface: "{{ private_interface }}"
|
|
||||||
direction: in
|
|
||||||
with_items:
|
|
||||||
- '3300'
|
|
||||||
- '6789'
|
|
||||||
|
|
||||||
- name: FIREWALL | Ceph OSD/MGR Port-Range auf öffentlichem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "6800:7568"
|
|
||||||
proto: tcp
|
|
||||||
interface: "{{ private_interface }}"
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: FIREWALL | Docker Swarm Management Ports auf privatem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "2377"
|
|
||||||
proto: tcp
|
|
||||||
interface: "{{ private_interface }}"
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: FIREWALL | Docker Swarm Discovery/Network Ports auf privatem Interface erlauben
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ item.port }}"
|
|
||||||
proto: "{{ item.proto }}"
|
|
||||||
interface: "{{ private_interface }}"
|
|
||||||
direction: in
|
|
||||||
with_items:
|
|
||||||
- { port: '7946', proto: 'tcp' }
|
|
||||||
- { port: '7946', proto: 'udp' }
|
|
||||||
- { port: '4789', proto: 'udp' }
|
|
||||||
|
|
||||||
- name: FIREWALL | UFW aktivieren
|
|
||||||
community.general.ufw:
|
|
||||||
state: enabled
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
config:
|
|
||||||
hcloud:token:
|
|
||||||
secure: AAABAGkphVtyNKTaI7oPUA7mZ4PehhSPRdIxfnEVdOc2Q+D+Q3bLpRofkiula4FSk+0bh8ModR46zmoDznrlrhqvhsm/mIcWmDOqKq+pyzRGrYKXpqi7sw1LhUanNV6Z
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
name: avicenna-infra
|
|
||||||
description: Infrastructure to host and run the Applications and Services developed for Avicenna
|
|
||||||
runtime: go
|
|
||||||
config:
|
|
||||||
pulumi:tags:
|
|
||||||
value:
|
|
||||||
pulumi:template: go
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
type InfraConfig struct {
|
|
||||||
StackReference string
|
|
||||||
SwarmNetworkName string
|
|
||||||
SwarmIpRange string
|
|
||||||
SwarmSubnetIpRange string
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
module avicenna-infra
|
|
||||||
|
|
||||||
go 1.24.0
|
|
||||||
|
|
||||||
require github.com/pulumi/pulumi/sdk/v3 v3.209.0
|
|
||||||
|
|
||||||
require (
|
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
|
||||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1 // indirect
|
|
||||||
github.com/charmbracelet/bubbletea v0.25.0 // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v0.7.1 // indirect
|
|
||||||
github.com/cheggaaa/pb v1.0.29 // indirect
|
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
|
||||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
|
||||||
github.com/djherbis/times v1.5.0 // indirect
|
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-git/go-billy/v5 v5.6.1 // indirect
|
|
||||||
github.com/go-git/go-git/v5 v5.13.1 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang/glog v1.2.4 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/opentracing/basictracer-go v1.1.0 // indirect
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
|
||||||
github.com/pgavlin/fx v0.1.6 // indirect
|
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pkg/term v1.1.0 // indirect
|
|
||||||
github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect
|
|
||||||
github.com/pulumi/esc v0.17.0 // indirect
|
|
||||||
github.com/pulumi/pulumi-command/sdk v1.1.3 // indirect
|
|
||||||
github.com/pulumi/pulumi-hcloud/sdk v1.29.0 // indirect
|
|
||||||
github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
|
||||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
|
||||||
github.com/spf13/cobra v1.8.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
|
|
||||||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
|
||||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
|
||||||
github.com/zclconf/go-cty v1.13.2 // indirect
|
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
|
||||||
golang.org/x/net v0.47.0 // indirect
|
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
|
||||||
golang.org/x/term v0.37.0 // indirect
|
|
||||||
golang.org/x/text v0.31.0 // indirect
|
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
|
||||||
google.golang.org/grpc v1.72.1 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
lukechampine.com/frand v1.4.2 // indirect
|
|
||||||
)
|
|
||||||
|
|
@ -1,309 +0,0 @@
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
|
||||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
|
||||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
|
||||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
|
||||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
|
||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
|
||||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
|
||||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
|
||||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
|
||||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
|
||||||
github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
|
|
||||||
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
|
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU=
|
|
||||||
github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0=
|
|
||||||
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
|
|
||||||
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
|
||||||
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
|
|
||||||
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
|
||||||
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
|
|
||||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
|
||||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
|
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
|
||||||
github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0=
|
|
||||||
github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc=
|
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
|
||||||
github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU=
|
|
||||||
github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M=
|
|
||||||
github.com/pgavlin/fx/v2 v2.0.3 h1:ZBVklTFjxcWvBVPE+ti5qwnmTIQ0Gq6nuj3J5RKDtKk=
|
|
||||||
github.com/pgavlin/fx/v2 v2.0.3/go.mod h1:Cvnwqq0BopdHUJ7CU50h1XPeKrF4ZwdFj1nJLXbAjCE=
|
|
||||||
github.com/pgavlin/fx/v2 v2.0.10 h1:ggyQ6pB+lEQEbEae48Wh/X221eLOamMD7i01ISe88u4=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
|
|
||||||
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0=
|
|
||||||
github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE=
|
|
||||||
github.com/pulumi/esc v0.17.0 h1:oaVOIyFTENlYDuqc3pW75lQT9jb2cd6ie/4/Twxn66w=
|
|
||||||
github.com/pulumi/esc v0.17.0/go.mod h1:XnSxlt5NkmuAj304l/gK4pRErFbtqq6XpfX1tYT9Jbc=
|
|
||||||
github.com/pulumi/pulumi-command/sdk v1.1.3 h1:2FdcqVenuHcGJfcVnUg6G22IeoQ/lY5UX6VexFJ4kT8=
|
|
||||||
github.com/pulumi/pulumi-command/sdk v1.1.3/go.mod h1:3ochnip+NSR3+lQh8//Cni6hR9ckswuc1c6URsmX4RM=
|
|
||||||
github.com/pulumi/pulumi-hcloud/sdk v1.29.0 h1:TQasXTpZlib8zCQXnsRSUpX0cB1/pRoPBIpPip0jwQc=
|
|
||||||
github.com/pulumi/pulumi-hcloud/sdk v1.29.0/go.mod h1:JID9el4HeuNGU9BJiPHQsGddjvkzoVbUUZtmD1dbuTg=
|
|
||||||
github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3 h1:HEHeifaSKme+mWXYkps5qYD45JJc86Y/0awBj7UC6vU=
|
|
||||||
github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3/go.mod h1:UxIBAyqaLIo5bn63paPM7FU7DahbfeSIKbgTf9KldLY=
|
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.209.0 h1:Ti0FohAset2HEgogTxTOWoyvQd2N2+pkTSIn5DW3W7s=
|
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.209.0/go.mod h1:qBMg01woyYVNqNDJXpFL1e5gdN7oQQvCNNNtfq1gsEo=
|
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
|
||||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=
|
|
||||||
github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=
|
|
||||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
|
||||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
|
||||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
|
||||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0=
|
|
||||||
github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
|
||||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
|
||||||
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
|
||||||
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
|
||||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
|
||||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw=
|
|
||||||
lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s=
|
|
||||||
pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA=
|
|
||||||
pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"avicenna-infra/config"
|
|
||||||
"avicenna-infra/utils"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pulumi/pulumi-command/sdk/go/command/remote"
|
|
||||||
"github.com/pulumi/pulumi-hcloud/sdk/go/hcloud"
|
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Infrastructure struct {
|
|
||||||
placementGroup *hcloud.PlacementGroup
|
|
||||||
networkID *pulumi.IDOutput
|
|
||||||
managerNodes []*hcloud.Server
|
|
||||||
workerNodes []*hcloud.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
infra := &Infrastructure{}
|
|
||||||
|
|
||||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cfg := config.InfraConfig{
|
|
||||||
StackReference: strings.Join(
|
|
||||||
[]string{ctx.Organization(), ctx.Project(), ctx.Stack()},
|
|
||||||
"/"),
|
|
||||||
SwarmNetworkName: "gc-swarmnet",
|
|
||||||
SwarmIpRange: "10.0.0.0/16",
|
|
||||||
SwarmSubnetIpRange: "10.0.1.0/24",
|
|
||||||
}
|
|
||||||
pk, err := utils.CreateSshKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
ctx.Export("privKey", pk.PrivateKeyOpenssh)
|
|
||||||
|
|
||||||
hkey, err := hcloud.NewSshKey(ctx, strings.Join([]string{cfg.StackReference, "key"}, "-"), &hcloud.SshKeyArgs{PublicKey: pk.PublicKeyOpenssh})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
infra.placementGroup, err = utils.CreatePlacementGroup(ctx, cfg.StackReference)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
infra.networkID, err = utils.CreateClusterNet(ctx, cfg)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
infra.managerNodes, err = utils.CreateServer(ctx, utils.CreateServerArgs{
|
|
||||||
PlacementGroupId: infra.placementGroup.ID(),
|
|
||||||
NetworkId: infra.networkID,
|
|
||||||
NetworkFirstIP: string(utils.IncrementIP(net.ParseIP("10.0.1.0"))),
|
|
||||||
Basename: "manager-node",
|
|
||||||
Count: 3,
|
|
||||||
SshKey: hkey,
|
|
||||||
ServerType: "ccx23",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
// infra.workerNodes, err = utils.CreateServer(ctx, utils.CreateServerArgs{
|
|
||||||
// PlacementGroupId: infra.placementGroup.ID(),
|
|
||||||
// NetworkId: infra.networkID,
|
|
||||||
// NetworkFirstIP: string(utils.IncrementIP(net.ParseIP("10.0.1.20"))),
|
|
||||||
// Basename: "worker-node",
|
|
||||||
// Count: 2,
|
|
||||||
// SshKey: hkey,
|
|
||||||
// })
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
for idx, s := range slices.Concat(infra.managerNodes, infra.workerNodes) {
|
|
||||||
err := utils.InstallAnsibleDependencies(ctx, remote.ConnectionArgs{
|
|
||||||
Host: s.Ipv4Address,
|
|
||||||
User: pulumi.String("root"),
|
|
||||||
PrivateKey: pk.PrivateKeyOpenssh,
|
|
||||||
}, fmt.Sprintf("%d", idx))
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// var advAddr = infra.managerNodes[0].Networks.ApplyT(func(net []hcloud.ServerNetworkType) string {
|
|
||||||
// return *net[0].Ip
|
|
||||||
// }).(pulumi.StringOutput)
|
|
||||||
|
|
||||||
// tokens, err := utils.InitDockerSwarm(ctx, remote.ConnectionArgs{
|
|
||||||
// Host: infra.managerNodes[0].Ipv4Address,
|
|
||||||
// User: pulumi.String("root"),
|
|
||||||
// PrivateKey: pk.PrivateKeyOpenssh}, advAddr)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ctx.Export("SwarmTokens", tokens)
|
|
||||||
|
|
||||||
// inventory, err := utils.CreateAnsibleInventory(infra.managerNodes, infra.workerNodes)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err.Error())
|
|
||||||
// }
|
|
||||||
// ctx.Export("inventory", inventory)
|
|
||||||
|
|
||||||
sm := map[string]pulumi.Input{}
|
|
||||||
for idx, s := range slices.Concat(infra.managerNodes, infra.workerNodes) {
|
|
||||||
sm[fmt.Sprintf("node-%d-ip", idx)] = s.Ipv4Address
|
|
||||||
}
|
|
||||||
ctx.Export("server-ips", pulumi.Map(sm))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/pulumi/pulumi-command/sdk/go/command/remote"
|
|
||||||
"github.com/pulumi/pulumi-hcloud/sdk/go/hcloud"
|
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SwarmJoinTokens struct {
|
|
||||||
ManagerToken string
|
|
||||||
WorkerToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerInfo struct {
|
|
||||||
Name pulumi.StringOutput
|
|
||||||
IP pulumi.StringOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
func InstallAnsibleDependencies(ctx *pulumi.Context, connArgs remote.ConnectionArgs, uniqueness string) error {
|
|
||||||
_, err := remote.NewCommand(ctx, strings.Join([]string{uniqueness, "Install Ansible Dependencies"}, ": "),
|
|
||||||
&remote.CommandArgs{
|
|
||||||
Connection: connArgs,
|
|
||||||
Create: pulumi.String("apt-get update && apt-get install -y python3-pip python3-jsondiff"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitDockerSwarm(ctx *pulumi.Context, connArgs remote.ConnectionArgs, advertiseAddr pulumi.StringOutput) (pulumi.Output, error) {
|
|
||||||
var tokens SwarmJoinTokens
|
|
||||||
|
|
||||||
fullCommand := advertiseAddr.ApplyT(func(addr string) *string {
|
|
||||||
initCommand := fmt.Sprintf("docker swarm init --advertise-addr %s", addr)
|
|
||||||
fullCommand := strings.Join([]string{initCommand, "echo \"Manager Token: $(docker swarm join-token -q manager)\"", "echo \"Worker Token: $(docker swarm join-token -q worker)\""}, " && ")
|
|
||||||
return &fullCommand
|
|
||||||
}).(pulumi.StringPtrOutput)
|
|
||||||
|
|
||||||
out, err := remote.NewCommand(ctx, "Init docker swarm",
|
|
||||||
&remote.CommandArgs{
|
|
||||||
Connection: connArgs,
|
|
||||||
Create: fullCommand,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return pulumi.StringOutput{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.Stdout.ApplyT(func(output string) SwarmJoinTokens {
|
|
||||||
searchWorker := "Worker Token: "
|
|
||||||
patternWorker := regexp.MustCompile(searchWorker + `(\S+)`)
|
|
||||||
searchManager := "Manager Token: "
|
|
||||||
patternManager := regexp.MustCompile(searchManager + `(\S+)`)
|
|
||||||
|
|
||||||
matches := patternWorker.FindStringSubmatch(output)
|
|
||||||
if len(matches) > 1 {
|
|
||||||
extracted := matches[1]
|
|
||||||
tokens.WorkerToken = extracted
|
|
||||||
}
|
|
||||||
matches = patternManager.FindStringSubmatch(output)
|
|
||||||
if len(matches) > 1 {
|
|
||||||
extracted := matches[1]
|
|
||||||
tokens.ManagerToken = extracted
|
|
||||||
}
|
|
||||||
return tokens
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateAnsibleInventory(managerNodes, workerNodes []*hcloud.Server) (pulumi.Output, error) {
|
|
||||||
serverInfos := toServerInfo(managerNodes)
|
|
||||||
return pulumi.All(pulumi.ToOutput(serverInfos)).ApplyT(func(results []interface{}) (string, error) {
|
|
||||||
var serverInfos = results[0].([]ServerInfo)
|
|
||||||
// var workerSlice = results[1].([]*hcloud.Server)
|
|
||||||
|
|
||||||
serverData := make(map[string][]ServerInfo)
|
|
||||||
|
|
||||||
for _, s := range serverInfos {
|
|
||||||
serverData["Manager"] = append(serverData["Manager"], ServerInfo{
|
|
||||||
Name: s.Name,
|
|
||||||
IP: s.IP,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// for _, result := range workerSlice {
|
|
||||||
// server := result.(map[string]interface{})
|
|
||||||
// serverData["Worker"] = append(serverData["Worker"], ServerInfo{
|
|
||||||
// Name: server["name"].(string),
|
|
||||||
// IP: server["ipv4_address"].(string),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
fmt.Println(serverData["Manager"])
|
|
||||||
fmt.Println(results[0])
|
|
||||||
return generateInventoryFile(serverData)
|
|
||||||
}).(pulumi.Output), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toServerInfo(server []*hcloud.Server) pulumi.ArrayOutput {
|
|
||||||
serverInfo := []ServerInfo{}
|
|
||||||
for _, s := range server {
|
|
||||||
serverInfo = append(serverInfo, ServerInfo{
|
|
||||||
Name: s.Name,
|
|
||||||
IP: s.Ipv4Address,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pulumi.All(serverInfo).ApplyT(func(args []interface{}) []interface{} {
|
|
||||||
var serverInfo []interface{}
|
|
||||||
|
|
||||||
for _, s := range args {
|
|
||||||
val := s.(map[string]interface{})
|
|
||||||
serverInfo = append(serverInfo, map[string]interface{}{
|
|
||||||
"Name": val["Name"].(string),
|
|
||||||
"IP": val["IP"].(string),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return serverInfo
|
|
||||||
}).(pulumi.ArrayOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateInventoryFile(inventory map[string][]ServerInfo) (string, error) {
|
|
||||||
const inventoryTmpl = `
|
|
||||||
[all]
|
|
||||||
{{ range .Manager }}
|
|
||||||
{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key
|
|
||||||
{{ end }}
|
|
||||||
{{ range .Worker }}
|
|
||||||
{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
[manager]
|
|
||||||
{{ range .Manager }}
|
|
||||||
{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
[worker]
|
|
||||||
{{ range .Worker }}
|
|
||||||
{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key
|
|
||||||
{{ end }}
|
|
||||||
`
|
|
||||||
tmpl, err := template.New("inventory").Parse(inventoryTmpl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err = tmpl.Execute(&buf, inventory)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"avicenna-infra/config"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/pulumi/pulumi-hcloud/sdk/go/hcloud"
|
|
||||||
"github.com/pulumi/pulumi-tls/sdk/v5/go/tls"
|
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateSshKey(ctx *pulumi.Context) (*tls.PrivateKey, error) {
|
|
||||||
return tls.NewPrivateKey(ctx, "sshKey", &tls.PrivateKeyArgs{
|
|
||||||
Algorithm: pulumi.String("ED25519"),
|
|
||||||
}, pulumi.AdditionalSecretOutputs([]string{"privKey"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreatePlacementGroup(ctx *pulumi.Context, name string) (*hcloud.PlacementGroup, error) {
|
|
||||||
pg, err := hcloud.NewPlacementGroup(ctx, name, &hcloud.PlacementGroupArgs{
|
|
||||||
Name: pulumi.String(name),
|
|
||||||
Type: pulumi.String("spread"),
|
|
||||||
})
|
|
||||||
return pg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateClusterNet(ctx *pulumi.Context, cfg config.InfraConfig) (*pulumi.IDOutput, error) {
|
|
||||||
var id pulumi.IDOutput
|
|
||||||
network, err := hcloud.NewNetwork(ctx, cfg.SwarmNetworkName, &hcloud.NetworkArgs{
|
|
||||||
Name: pulumi.String(cfg.SwarmNetworkName),
|
|
||||||
IpRange: pulumi.String(cfg.SwarmIpRange),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
id = network.ID()
|
|
||||||
_, err = hcloud.NewNetworkSubnet(ctx, "network-subnet", &hcloud.NetworkSubnetArgs{
|
|
||||||
Type: pulumi.String("cloud"),
|
|
||||||
NetworkId: IDtoIntOutput(id),
|
|
||||||
NetworkZone: pulumi.String("eu-central"),
|
|
||||||
IpRange: pulumi.String(cfg.SwarmSubnetIpRange),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateServerArgs struct {
|
|
||||||
NetworkId pulumi.IDInput
|
|
||||||
PlacementGroupId pulumi.IDInput
|
|
||||||
// The first assignable IP of the network
|
|
||||||
NetworkFirstIP string
|
|
||||||
Basename string
|
|
||||||
Count int
|
|
||||||
SshKey *hcloud.SshKey
|
|
||||||
ServerType string
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateServer(ctx *pulumi.Context, cfg CreateServerArgs) ([]*hcloud.Server, error) {
|
|
||||||
var nodes []*hcloud.Server
|
|
||||||
nextIp := cfg.NetworkFirstIP
|
|
||||||
for i := range cfg.Count {
|
|
||||||
sn := fmt.Sprintf("%s-%d", cfg.Basename, i+1)
|
|
||||||
s, err := hcloud.NewServer(ctx, sn, &hcloud.ServerArgs{
|
|
||||||
Name: pulumi.String(sn),
|
|
||||||
Image: pulumi.String("docker-ce"),
|
|
||||||
ServerType: pulumi.String(cfg.ServerType),
|
|
||||||
Location: pulumi.StringPtr("fsn1"),
|
|
||||||
Networks: hcloud.ServerNetworkTypeArray{
|
|
||||||
&hcloud.ServerNetworkTypeArgs{
|
|
||||||
NetworkId: IDtoIntOutput(cfg.NetworkId),
|
|
||||||
Ip: pulumi.String(nextIp),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PlacementGroupId: IDtoIntPtrOutput(cfg.PlacementGroupId),
|
|
||||||
PublicNets: hcloud.ServerPublicNetArray{
|
|
||||||
&hcloud.ServerPublicNetArgs{
|
|
||||||
// Ipv4Enabled: pulumi.Bool(true),
|
|
||||||
Ipv6Enabled: pulumi.Bool(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SshKeys: pulumi.StringArray{cfg.SshKey.ID()},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nodes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cephVolume, err := hcloud.NewVolume(ctx, fmt.Sprintf("ceph-%s", sn), &hcloud.VolumeArgs{
|
|
||||||
Name: pulumi.Sprintf("%s-ceph-vol-0%d", s.Name, i+1),
|
|
||||||
Size: pulumi.Int(100),
|
|
||||||
Location: s.Location,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nodes, fmt.Errorf("couldn't create volume: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = hcloud.NewVolumeAttachment(ctx, fmt.Sprintf("ceph-vol-attach-%s", sn), &hcloud.VolumeAttachmentArgs{
|
|
||||||
VolumeId: IDtoIntOutput(cephVolume.ID()),
|
|
||||||
ServerId: IDtoIntOutput(s.ID()),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nodes, fmt.Errorf("couldn't attach volume to node %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes = append(nodes, s)
|
|
||||||
nextIp = IncrementIP(net.ParseIP(nextIp)).String()
|
|
||||||
}
|
|
||||||
return nodes, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IncrementIP(ip net.IP) net.IP {
|
|
||||||
if ip == nil {
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
ip = ip.To4()
|
|
||||||
ip[3]++
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
func IDtoInt(val string) (int, error) {
|
|
||||||
i, err := strconv.Atoi(val)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func IDtoIntPtr(val string) (*int, error) {
|
|
||||||
i, err := strconv.Atoi(val)
|
|
||||||
return &i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert pulumi.IDOutput to pulumi.InOutput
|
|
||||||
//
|
|
||||||
// Some hcloud ID's are int based, but pulumi.IDOutput doesn't have a ToIntOutput method
|
|
||||||
func IDtoIntOutput(val pulumi.IDInput) pulumi.IntOutput {
|
|
||||||
return val.ToIDOutput().ToStringOutput().ApplyT(IDtoInt).(pulumi.IntOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert pulumi.IDOutput to pulumi.InOutput
|
|
||||||
//
|
|
||||||
// Some hcloud ID's are int based, but pulumi.IDOutput doesn't have a ToIntOutput method
|
|
||||||
func IDtoIntPtrOutput(val pulumi.IDInput) pulumi.IntPtrOutput {
|
|
||||||
return val.ToIDOutput().ToStringOutput().ApplyT(IDtoIntPtr).(pulumi.IntPtrOutput)
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
/bin/
|
|
||||||
node_modules/
|
|
||||||
.local/
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Infrastructure Project
|
|
||||||
|
|
||||||
Dieses Projekt erstellt einen Hetzner Server mit Docker vorinstalliert.
|
|
||||||
|
|
||||||
## Verwendung
|
|
||||||
|
|
||||||
Hier liegen die SSH Schlüssel für die ursprüngliche Avicenna Produktionsumgebung.
|
|
||||||
Desweiteren liegt hier ein Schlüssel von Factorypuls, vermutlich der Schlüssel für den Hetzner Server, welcher das Visual hostet.
|
|
||||||
Binary file not shown.
|
|
@ -1,5 +0,0 @@
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
.git
|
|
||||||
.local
|
|
||||||
.vscode
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
|
|
||||||
.angular
|
|
||||||
.vscode
|
|
||||||
dist
|
|
||||||
node_modules
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.html",
|
|
||||||
"options": {
|
|
||||||
"parser": "angular"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +1,18 @@
|
||||||
FROM node:20.19.1-slim AS builder
|
FROM node:20.15.1-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app/builder
|
WORKDIR /app/builder
|
||||||
|
|
||||||
COPY package*.json .
|
COPY . .
|
||||||
COPY packages/shared-dtos/package*.json ./packages/shared-dtos/
|
|
||||||
# COPY data-hub/data-connector/package*.json ./data-hub/data-connector/
|
|
||||||
COPY app-hub/dashboard/package*.json ./app-hub/dashboard/
|
|
||||||
|
|
||||||
ENV LIBC=glibc
|
RUN npm install \
|
||||||
ENV npm_config_arch=x64
|
&& npm run build
|
||||||
ENV npm_config_platform=linux
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
python3 \
|
|
||||||
make \
|
|
||||||
g++ \
|
|
||||||
ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN npm ci --verbose
|
|
||||||
|
|
||||||
COPY packages/shared-dtos ./packages/shared-dtos/
|
|
||||||
COPY app-hub/dashboard ./app-hub/dashboard/
|
|
||||||
|
|
||||||
WORKDIR /app/builder/app-hub/dashboard
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM nginx:1.19.2
|
FROM nginx:1.19.2
|
||||||
|
|
||||||
WORKDIR /usr/share/nginx/html
|
WORKDIR /usr/share/nginx/html
|
||||||
COPY --from=builder /app/builder/app-hub/dashboard/dist/dashboard/browser ./
|
COPY --from=builder /app/builder/dist/dashboard/browser ./
|
||||||
COPY --from=builder /app/builder/app-hub/dashboard/nginx.conf /etc/nginx/nginx.conf
|
COPY --from=builder /app/builder/nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY --from=builder /app/builder/app-hub/dashboard/docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --from=builder /app/builder/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"componentsPath": "src/app/core/base-components",
|
|
||||||
"importAlias": "@spartan-ng/helm"
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -xe
|
set -xe
|
||||||
: "${API_BASEURL?API_BASEURL is not provided}"
|
|
||||||
: "${GRAPHQL_ENDPOINT?GRAPHQL_ENDPOINT is not provided}"
|
: "${GRAPHQL_ENDPOINT?GRAPHQL_ENDPOINT is not provided}"
|
||||||
: "${GRAPHQL_WS_ENDPOINT?GRAPHQL_WS_ENDPOINT is not provided}"
|
: "${GRAPHQL_WS_ENDPOINT?GRAPHQL_WS_ENDPOINT is not provided}"
|
||||||
: "${MS_AUTHENTICATION_CLIENT_ID?MS_AUTHENTICATION_CLIENT_ID is not provided}"
|
: "${MS_AUTHENTICATION_CLIENT_ID?MS_AUTHENTICATION_CLIENT_ID is not provided}"
|
||||||
|
|
@ -11,14 +10,14 @@ set -xe
|
||||||
: "${OTLP_SERVICE_NAME?OTLP_SERVICE_NAME is not provided}"
|
: "${OTLP_SERVICE_NAME?OTLP_SERVICE_NAME is not provided}"
|
||||||
: "${COLLECTOR_SECRET?COLLECTOR_SECRET is not provided}"
|
: "${COLLECTOR_SECRET?COLLECTOR_SECRET is not provided}"
|
||||||
|
|
||||||
env_vars=("API_BASEURL" "GRAPHQL_ENDPOINT" "GRAPHQL_WS_ENDPOINT" "MS_AUTHENTICATION_CLIENT_ID" "MS_AUTHENTICATION_AUTHORITY" "MS_AUTHENTICATION_REDIRECT_URI" "MS_AUTHENTICATION_POST_LOGOUT_REDIRECT_URI" "OTLP_ENDPOINT" "OTLP_SERVICE_NAME" "COLLECTOR_SECRET")
|
env_vars=("GRAPHQL_ENDPOINT" "GRAPHQL_WS_ENDPOINT" "MS_AUTHENTICATION_CLIENT_ID" "MS_AUTHENTICATION_AUTHORITY" "MS_AUTHENTICATION_REDIRECT_URI" "MS_AUTHENTICATION_POST_LOGOUT_REDIRECT_URI" "OTLP_ENDPOINT" "OTLP_SERVICE_NAME" "COLLECTOR_SECRET")
|
||||||
|
|
||||||
ls /usr/share/nginx/html/
|
ls /usr/share/nginx/html/
|
||||||
|
|
||||||
for var in "${env_vars[@]}"
|
for var in "${env_vars[@]}"
|
||||||
do
|
do
|
||||||
echo "$var ${!var}"
|
echo "$var ${!var}"
|
||||||
sed -i "s~{{$var}}~${!var}~gi" /usr/share/nginx/html/*.js
|
sed -i "s~{{$var}}~${!var}~gi" /usr/share/nginx/html/main*.js
|
||||||
done
|
done
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -23,12 +23,10 @@
|
||||||
"@angular/platform-browser-dynamic": "^19.2.6",
|
"@angular/platform-browser-dynamic": "^19.2.6",
|
||||||
"@angular/router": "^19.2.6",
|
"@angular/router": "^19.2.6",
|
||||||
"@apollo/client": "^3.13.8",
|
"@apollo/client": "^3.13.8",
|
||||||
"@avicenna/shared-dtos": "^1.0.0",
|
|
||||||
"@azure/msal-angular": "^3.0.22",
|
"@azure/msal-angular": "^3.0.22",
|
||||||
"@azure/msal-browser": "^3.19.1",
|
"@azure/msal-browser": "^3.19.1",
|
||||||
"@hyperdx/browser": "^0.22.0",
|
"@grafana/faro-web-sdk": "^1.15.0",
|
||||||
"@ng-icons/core": "^31.4.0",
|
"@grafana/faro-web-tracing": "^1.15.0",
|
||||||
"@ng-icons/lucide": "^31.4.0",
|
|
||||||
"@ngneat/dialog": "^5.1.1",
|
"@ngneat/dialog": "^5.1.1",
|
||||||
"@ngneat/overview": "^6.0.0",
|
"@ngneat/overview": "^6.0.0",
|
||||||
"@ngxpert/hot-toast": "^3.0.0",
|
"@ngxpert/hot-toast": "^3.0.0",
|
||||||
|
|
@ -43,18 +41,12 @@
|
||||||
"@opentelemetry/resources": "^1.30.1",
|
"@opentelemetry/resources": "^1.30.1",
|
||||||
"@opentelemetry/sdk-trace-web": "^1.30.1",
|
"@opentelemetry/sdk-trace-web": "^1.30.1",
|
||||||
"@opentelemetry/semantic-conventions": "^1.34.0",
|
"@opentelemetry/semantic-conventions": "^1.34.0",
|
||||||
"@spartan-ng/brain": "^0.0.1-alpha.561",
|
|
||||||
"@tanstack/angular-query-experimental": "^5.90.20",
|
|
||||||
"apollo-angular": "^10.0.3",
|
"apollo-angular": "^10.0.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
"graphql-ws": "^5.16.0",
|
"graphql-ws": "^5.16.0",
|
||||||
"ngx-scrollbar": "^19.1.1",
|
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
|
|
@ -68,7 +60,6 @@
|
||||||
"@graphql-codegen/typescript-apollo-angular": "^4.0.0",
|
"@graphql-codegen/typescript-apollo-angular": "^4.0.0",
|
||||||
"@graphql-codegen/typescript-operations": "^4.6.0",
|
"@graphql-codegen/typescript-operations": "^4.6.0",
|
||||||
"@material/material-color-utilities": "^0.3.0",
|
"@material/material-color-utilities": "^0.3.0",
|
||||||
"@spartan-ng/cli": "^0.0.1-alpha.561",
|
|
||||||
"@tailwindcss/postcss": "^4.1.4",
|
"@tailwindcss/postcss": "^4.1.4",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
|
|
@ -79,9 +70,7 @@
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"prettier": "^3.8.0",
|
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"tw-animate-css": "^1.4.0",
|
|
||||||
"typescript": "~5.5.2"
|
"typescript": "~5.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,17 @@ import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { AppShellComponent } from './core/components/app-shell/app-shell.component';
|
import { AppShellComponent } from './core/components/app-shell/app-shell.component';
|
||||||
import { MsalService } from '@azure/msal-angular';
|
import { MsalService } from '@azure/msal-angular';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [AppShellComponent, RouterOutlet],
|
imports: [AppShellComponent, RouterOutlet],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss'
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'dashboard';
|
title = 'dashboard';
|
||||||
|
|
||||||
constructor(private readonly authService: MsalService) {
|
constructor(private readonly authService: MsalService) {
|
||||||
this.authService
|
this.authService.handleRedirectObservable().subscribe();
|
||||||
.handleRedirectObservable()
|
|
||||||
.pipe(takeUntilDestroyed())
|
|
||||||
.subscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,40 +9,18 @@ import { provideRouter, withComponentInputBinding } from '@angular/router';
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
||||||
import {
|
|
||||||
provideTanStackQuery,
|
|
||||||
QueryClient,
|
|
||||||
} from '@tanstack/angular-query-experimental';
|
|
||||||
|
|
||||||
import { withDevtools } from '@tanstack/angular-query-experimental/devtools';
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { MsAuthenticationModule } from './core/components/ms-authentication/ms-authentication.module';
|
import { MsAuthenticationModule } from './core/components/ms-authentication/ms-authentication.module';
|
||||||
import { GlobalErrorHandler } from './core/telemetry/global-error-handler';
|
import { GlobalErrorHandler } from './core/telemetry/global-error-handler';
|
||||||
import { graphqlProvider } from './graphql.provider';
|
import { graphqlProvider } from './graphql.provider';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
// Daten gelten für 2 Minute als "frisch" (kein Refetch)
|
|
||||||
staleTime: 2000 * 60 * 1,
|
|
||||||
retry: 2,
|
|
||||||
refetchOnWindowFocus: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
__TANSTACK_QUERY_CLIENT__: import('@tanstack/query-core').QueryClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code is for all users
|
|
||||||
window.__TANSTACK_QUERY_CLIENT__ = queryClient;
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
// provideAppInitializer(() => {
|
||||||
|
// const initializerFn = faroInitializer();
|
||||||
|
// return initializerFn();
|
||||||
|
// }),
|
||||||
{
|
{
|
||||||
provide: ErrorHandler,
|
provide: ErrorHandler,
|
||||||
useClass: GlobalErrorHandler,
|
useClass: GlobalErrorHandler,
|
||||||
|
|
@ -58,7 +36,6 @@ export const appConfig: ApplicationConfig = {
|
||||||
postLogoutRedirectUri: environment.msAuth.postLogoutRedirectUri,
|
postLogoutRedirectUri: environment.msAuth.postLogoutRedirectUri,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
provideTanStackQuery(queryClient, withDevtools()),
|
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
graphqlProvider,
|
graphqlProvider,
|
||||||
provideHotToastConfig(),
|
provideHotToastConfig(),
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'tickets',
|
path: 'tickets',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/tickets/ticket-system.module').then(
|
import('./pages/ticket-system/ticket-system.module').then(
|
||||||
(m) => m.TicketSystemModule
|
(m) => m.TicketSystemModule
|
||||||
),
|
),
|
||||||
canActivate: [MsalGuard],
|
canActivate: [MsalGuard],
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { HlmAlertDialog } from './lib/hlm-alert-dialog';
|
|
||||||
import { HlmAlertDialogActionButton } from './lib/hlm-alert-dialog-action-button';
|
|
||||||
import { HlmAlertDialogCancelButton } from './lib/hlm-alert-dialog-cancel-button';
|
|
||||||
import { HlmAlertDialogContent } from './lib/hlm-alert-dialog-content';
|
|
||||||
import { HlmAlertDialogDescription } from './lib/hlm-alert-dialog-description';
|
|
||||||
import { HlmAlertDialogFooter } from './lib/hlm-alert-dialog-footer';
|
|
||||||
import { HlmAlertDialogHeader } from './lib/hlm-alert-dialog-header';
|
|
||||||
import { HlmAlertDialogOverlay } from './lib/hlm-alert-dialog-overlay';
|
|
||||||
import { HlmAlertDialogTitle } from './lib/hlm-alert-dialog-title';
|
|
||||||
|
|
||||||
export * from './lib/hlm-alert-dialog';
|
|
||||||
export * from './lib/hlm-alert-dialog-action-button';
|
|
||||||
export * from './lib/hlm-alert-dialog-cancel-button';
|
|
||||||
export * from './lib/hlm-alert-dialog-content';
|
|
||||||
export * from './lib/hlm-alert-dialog-description';
|
|
||||||
export * from './lib/hlm-alert-dialog-footer';
|
|
||||||
export * from './lib/hlm-alert-dialog-header';
|
|
||||||
export * from './lib/hlm-alert-dialog-overlay';
|
|
||||||
export * from './lib/hlm-alert-dialog-title';
|
|
||||||
|
|
||||||
export const HlmAlertDialogImports = [
|
|
||||||
HlmAlertDialogContent,
|
|
||||||
HlmAlertDialogDescription,
|
|
||||||
HlmAlertDialogFooter,
|
|
||||||
HlmAlertDialogHeader,
|
|
||||||
HlmAlertDialogOverlay,
|
|
||||||
HlmAlertDialogTitle,
|
|
||||||
HlmAlertDialogActionButton,
|
|
||||||
HlmAlertDialogCancelButton,
|
|
||||||
HlmAlertDialog,
|
|
||||||
] as const;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Directive } from '@angular/core';
|
|
||||||
import { HlmButton } from '@spartan-ng/helm/button';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: 'button[hlmAlertDialogAction]',
|
|
||||||
hostDirectives: [{ directive: HlmButton, inputs: ['variant', 'size'] }],
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogActionButton {}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { Directive } from '@angular/core';
|
|
||||||
import { HlmButton, provideBrnButtonConfig } from '@spartan-ng/helm/button';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: 'button[hlmAlertDialogCancel]',
|
|
||||||
providers: [provideBrnButtonConfig({ variant: 'outline' })],
|
|
||||||
hostDirectives: [{ directive: HlmButton, inputs: ['variant', 'size'] }],
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogCancelButton {}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
|
|
||||||
import { injectExposesStateProvider } from '@spartan-ng/brain/core';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-alert-dialog-content',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
'[attr.data-state]': 'state()',
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<ng-content />
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogContent {
|
|
||||||
private readonly _stateProvider = injectExposesStateProvider({ optional: true, host: true });
|
|
||||||
public readonly state = this._stateProvider?.state ?? signal('closed');
|
|
||||||
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm(
|
|
||||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 relative z-50 mx-auto grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border p-6 shadow-lg data-[state=closed]:duration-200 data-[state=open]:duration-200 sm:mx-0 sm:max-w-lg',
|
|
||||||
this.userClass(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { Directive, computed, input } from '@angular/core';
|
|
||||||
import { BrnAlertDialogDescription } from '@spartan-ng/brain/alert-dialog';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[hlmAlertDialogDescription]',
|
|
||||||
hostDirectives: [BrnAlertDialogDescription],
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogDescription {
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() => hlm('text-muted-foreground text-sm', this.userClass()));
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-alert-dialog-footer',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<ng-content />
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogFooter {
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', this.userClass()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-alert-dialog-header',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<ng-content />
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogHeader {
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm('flex flex-col gap-2 text-center sm:text-left', this.userClass()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { Directive, computed, effect, input, untracked } from '@angular/core';
|
|
||||||
import { injectCustomClassSettable } from '@spartan-ng/brain/core';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[hlmAlertDialogOverlay],brn-alert-dialog-overlay[hlm]',
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogOverlay {
|
|
||||||
private readonly _classSettable = injectCustomClassSettable({ optional: true, host: true });
|
|
||||||
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm(
|
|
||||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-black/50',
|
|
||||||
this.userClass(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
effect(() => {
|
|
||||||
const classValue = this._computedClass();
|
|
||||||
untracked(() => this._classSettable?.setClassToCustomElement(classValue));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { Directive, computed, input } from '@angular/core';
|
|
||||||
import { BrnAlertDialogTitle } from '@spartan-ng/brain/alert-dialog';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[hlmAlertDialogTitle]',
|
|
||||||
hostDirectives: [BrnAlertDialogTitle],
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export class HlmAlertDialogTitle {
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() => hlm('text-lg font-semibold', this.userClass()));
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core';
|
|
||||||
import {
|
|
||||||
BRN_ALERT_DIALOG_DEFAULT_OPTIONS,
|
|
||||||
BrnAlertDialog,
|
|
||||||
BrnAlertDialogOverlay,
|
|
||||||
} from '@spartan-ng/brain/alert-dialog';
|
|
||||||
import { BrnDialog, provideBrnDialogDefaultOptions } from '@spartan-ng/brain/dialog';
|
|
||||||
import { HlmAlertDialogOverlay } from './hlm-alert-dialog-overlay';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-alert-dialog',
|
|
||||||
exportAs: 'hlmAlertDialog',
|
|
||||||
imports: [BrnAlertDialogOverlay, HlmAlertDialogOverlay],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: BrnDialog,
|
|
||||||
useExisting: forwardRef(() => HlmAlertDialog),
|
|
||||||
},
|
|
||||||
provideBrnDialogDefaultOptions({
|
|
||||||
...BRN_ALERT_DIALOG_DEFAULT_OPTIONS,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
template: `
|
|
||||||
<brn-alert-dialog-overlay hlm />
|
|
||||||
<ng-content />
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAlertDialog extends BrnAlertDialog {}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { HlmAutocomplete } from './lib/hlm-autocomplete';
|
|
||||||
import { HlmAutocompleteEmpty } from './lib/hlm-autocomplete-empty';
|
|
||||||
import { HlmAutocompleteGroup } from './lib/hlm-autocomplete-group';
|
|
||||||
import { HlmAutocompleteItem } from './lib/hlm-autocomplete-item';
|
|
||||||
import { HlmAutocompleteList } from './lib/hlm-autocomplete-list';
|
|
||||||
import { HlmAutocompleteTrigger } from './lib/hlm-autocomplete-trigger';
|
|
||||||
|
|
||||||
export * from './lib/hlm-autocomplete';
|
|
||||||
export * from './lib/hlm-autocomplete-empty';
|
|
||||||
export * from './lib/hlm-autocomplete-group';
|
|
||||||
export * from './lib/hlm-autocomplete-item';
|
|
||||||
export * from './lib/hlm-autocomplete-list';
|
|
||||||
export * from './lib/hlm-autocomplete-trigger';
|
|
||||||
export * from './lib/hlm-autocomplete.token';
|
|
||||||
|
|
||||||
export const HlmAutocompleteImports = [
|
|
||||||
HlmAutocomplete,
|
|
||||||
HlmAutocompleteEmpty,
|
|
||||||
HlmAutocompleteGroup,
|
|
||||||
HlmAutocompleteItem,
|
|
||||||
HlmAutocompleteList,
|
|
||||||
HlmAutocompleteTrigger,
|
|
||||||
] as const;
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { Directive, computed, input } from '@angular/core';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[hlmAutocompleteEmpty]',
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export class HlmAutocompleteEmpty {
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() => hlm('py-6 text-center text-sm', this.userClass()));
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
||||||
import { BrnAutocompleteGroup } from '@spartan-ng/brain/autocomplete';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-autocomplete-group',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
hostDirectives: [
|
|
||||||
{
|
|
||||||
directive: BrnAutocompleteGroup,
|
|
||||||
inputs: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: '<ng-content />',
|
|
||||||
})
|
|
||||||
export class HlmAutocompleteGroup {
|
|
||||||
/** The user defined class */
|
|
||||||
public readonly userClass = input<string>('', { alias: 'class' });
|
|
||||||
|
|
||||||
/** The styles to apply */
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm('text-foreground block overflow-hidden p-1', this.userClass()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
||||||
import { BrnAutocompleteItem } from '@spartan-ng/brain/autocomplete';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'button[hlm-autocomplete-item]',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
hostDirectives: [
|
|
||||||
{
|
|
||||||
directive: BrnAutocompleteItem,
|
|
||||||
inputs: ['value', 'disabled', 'id'],
|
|
||||||
outputs: ['selected'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<ng-content />
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAutocompleteItem {
|
|
||||||
/** The user defined class */
|
|
||||||
public readonly userClass = input<string>('', { alias: 'class' });
|
|
||||||
|
|
||||||
/** The styles to apply */
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm(
|
|
||||||
'data-[selected]:bg-accent data-[selected=true]:text-accent-foreground [&>ng-icon]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-start text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[hidden]:hidden [&>ng-icon]:pointer-events-none [&>ng-icon]:shrink-0 [&>ng-icon]:text-base',
|
|
||||||
this.userClass(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
||||||
import { BrnAutocompleteList } from '@spartan-ng/brain/autocomplete';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-autocomplete-list',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
hostDirectives: [
|
|
||||||
{
|
|
||||||
directive: BrnAutocompleteList,
|
|
||||||
inputs: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: '<ng-content />',
|
|
||||||
})
|
|
||||||
export class HlmAutocompleteList {
|
|
||||||
/** The user defined class */
|
|
||||||
public readonly userClass = input<string>('', { alias: 'class' });
|
|
||||||
|
|
||||||
/** The styles to apply */
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm('block max-h-60 overflow-x-hidden overflow-y-auto', this.userClass()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import type { BooleanInput } from '@angular/cdk/coercion';
|
|
||||||
import { booleanAttribute, Directive, ElementRef, inject, input } from '@angular/core';
|
|
||||||
import { BrnDialog } from '@spartan-ng/brain/dialog';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[hlmAutocompleteTrigger]',
|
|
||||||
host: {
|
|
||||||
'(click)': 'open()',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export class HlmAutocompleteTrigger {
|
|
||||||
private readonly _host = inject(ElementRef, { host: true });
|
|
||||||
|
|
||||||
private readonly _brnDialog = inject(BrnDialog, { optional: true });
|
|
||||||
|
|
||||||
/** Whether the trigger is disabled. */
|
|
||||||
public readonly disabledTrigger = input<boolean, BooleanInput>(false, {
|
|
||||||
transform: booleanAttribute,
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (!this._brnDialog) return;
|
|
||||||
|
|
||||||
this._brnDialog.mutableAttachTo.set(this._host.nativeElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
open() {
|
|
||||||
if (this.disabledTrigger()) return;
|
|
||||||
|
|
||||||
this._brnDialog?.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import { inject, InjectionToken, type ValueProvider } from '@angular/core';
|
|
||||||
|
|
||||||
export type TransformValueToString<T> = (option: T) => string;
|
|
||||||
|
|
||||||
export interface HlmAutocompleteConfig<T, V = T> {
|
|
||||||
transformValueToSearch: TransformValueToString<T>;
|
|
||||||
transformOptionToString: TransformValueToString<T>;
|
|
||||||
transformOptionToValue: ((option: T) => V) | undefined;
|
|
||||||
requireSelection: boolean;
|
|
||||||
showClearBtn: boolean;
|
|
||||||
debounceTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultConfig<T, V = T>(): HlmAutocompleteConfig<T, V> {
|
|
||||||
return {
|
|
||||||
transformValueToSearch: (option: T) => (typeof option === 'string' ? option : String(option)),
|
|
||||||
transformOptionToString: (option: T) => (typeof option === 'string' ? option : String(option)),
|
|
||||||
transformOptionToValue: undefined,
|
|
||||||
requireSelection: false,
|
|
||||||
showClearBtn: false,
|
|
||||||
debounceTime: 150,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const HlmAutocompleteConfigToken = new InjectionToken<HlmAutocompleteConfig<unknown, unknown>>('HlmAutocompleteConfig');
|
|
||||||
|
|
||||||
export function provideHlmAutocompleteConfig<T, V = T>(config: Partial<HlmAutocompleteConfig<T, V>>): ValueProvider {
|
|
||||||
return { provide: HlmAutocompleteConfigToken, useValue: { ...getDefaultConfig(), ...config } };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function injectHlmAutocompleteConfig<T, V = T>(): HlmAutocompleteConfig<T, V> {
|
|
||||||
return (
|
|
||||||
(inject(HlmAutocompleteConfigToken, { optional: true }) as HlmAutocompleteConfig<T, V> | null) ?? getDefaultConfig()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,426 +0,0 @@
|
||||||
import type { BooleanInput } from '@angular/cdk/coercion';
|
|
||||||
import { NgTemplateOutlet } from '@angular/common';
|
|
||||||
import {
|
|
||||||
booleanAttribute,
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
computed,
|
|
||||||
effect,
|
|
||||||
ElementRef,
|
|
||||||
forwardRef,
|
|
||||||
inject,
|
|
||||||
input,
|
|
||||||
linkedSignal,
|
|
||||||
model,
|
|
||||||
output,
|
|
||||||
type TemplateRef,
|
|
||||||
viewChild,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
||||||
import { provideIcons } from '@ng-icons/core';
|
|
||||||
import {
|
|
||||||
lucideChevronDown,
|
|
||||||
lucideCircleX,
|
|
||||||
lucideSearch,
|
|
||||||
} from '@ng-icons/lucide';
|
|
||||||
import {
|
|
||||||
BrnAutocomplete,
|
|
||||||
BrnAutocompleteEmpty,
|
|
||||||
BrnAutocompleteImports,
|
|
||||||
} from '@spartan-ng/brain/autocomplete';
|
|
||||||
import { debouncedSignal } from '@spartan-ng/brain/core';
|
|
||||||
import type { ChangeFn, TouchFn } from '@spartan-ng/brain/forms';
|
|
||||||
import { BrnPopoverImports } from '@spartan-ng/brain/popover';
|
|
||||||
import { HlmIconImports } from '@spartan-ng/helm/icon';
|
|
||||||
import { HlmPopoverImports } from '@spartan-ng/helm/popover';
|
|
||||||
import { hlm } from '@spartan-ng/helm/utils';
|
|
||||||
import type { ClassValue } from 'clsx';
|
|
||||||
import { HlmAutocompleteEmpty } from './hlm-autocomplete-empty';
|
|
||||||
import { HlmAutocompleteGroup } from './hlm-autocomplete-group';
|
|
||||||
import { HlmAutocompleteItem } from './hlm-autocomplete-item';
|
|
||||||
import { HlmAutocompleteList } from './hlm-autocomplete-list';
|
|
||||||
|
|
||||||
import { HlmInputGroupImports } from '@spartan-ng/helm/input-group';
|
|
||||||
import { HlmAutocompleteTrigger } from './hlm-autocomplete-trigger';
|
|
||||||
import { injectHlmAutocompleteConfig } from './hlm-autocomplete.token';
|
|
||||||
|
|
||||||
export const HLM_AUTOCOMPLETE_VALUE_ACCESSOR = {
|
|
||||||
provide: NG_VALUE_ACCESSOR,
|
|
||||||
useExisting: forwardRef(() => HlmAutocomplete),
|
|
||||||
multi: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hlm-autocomplete',
|
|
||||||
imports: [
|
|
||||||
NgTemplateOutlet,
|
|
||||||
BrnAutocomplete,
|
|
||||||
BrnAutocompleteEmpty,
|
|
||||||
HlmAutocompleteEmpty,
|
|
||||||
HlmAutocompleteGroup,
|
|
||||||
HlmAutocompleteItem,
|
|
||||||
HlmAutocompleteList,
|
|
||||||
HlmAutocompleteTrigger,
|
|
||||||
BrnPopoverImports,
|
|
||||||
HlmPopoverImports,
|
|
||||||
HlmIconImports,
|
|
||||||
HlmInputGroupImports,
|
|
||||||
BrnAutocompleteImports,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
HLM_AUTOCOMPLETE_VALUE_ACCESSOR,
|
|
||||||
provideIcons({ lucideSearch, lucideChevronDown, lucideCircleX }),
|
|
||||||
],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
host: {
|
|
||||||
'[class]': '_computedClass()',
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
@let transformer = transformOptionToValue();
|
|
||||||
<brn-popover
|
|
||||||
#popover
|
|
||||||
align="start"
|
|
||||||
autoFocus="first-heading"
|
|
||||||
sideOffset="5"
|
|
||||||
closeDelay="100"
|
|
||||||
(closed)="_closed()"
|
|
||||||
[closeOnOutsidePointerEvents]="true"
|
|
||||||
>
|
|
||||||
<div brnAutocomplete (selectionCleared)="_selectionCleared()">
|
|
||||||
<div
|
|
||||||
hlmInputGroup
|
|
||||||
hlmAutocompleteTrigger
|
|
||||||
[class]="_computedAutocompleteSearchClass()"
|
|
||||||
[disabledTrigger]="!_search() || _disabled()"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
#input
|
|
||||||
brnAutocompleteSearchInput
|
|
||||||
hlmInputGroupInput
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
[id]="inputId()"
|
|
||||||
[class]="_computedAutocompleteInputClass()"
|
|
||||||
[placeholder]="searchPlaceholderText()"
|
|
||||||
[disabled]="_disabled()"
|
|
||||||
[value]="search()"
|
|
||||||
(input)="_searchChanged($event)"
|
|
||||||
data-bwignore="true"
|
|
||||||
data-1p-ignore="true"
|
|
||||||
data-lpignore="true"
|
|
||||||
/>
|
|
||||||
<div hlmInputGroupAddon>
|
|
||||||
<ng-icon name="lucideSearch" />
|
|
||||||
</div>
|
|
||||||
<div hlmInputGroupAddon align="inline-end">
|
|
||||||
@if (showClearBtn() && value() !== undefined) {
|
|
||||||
<button
|
|
||||||
hlmInputGroupButton
|
|
||||||
type="button"
|
|
||||||
tabindex="-1"
|
|
||||||
aria-label="clear"
|
|
||||||
[disabled]="_disabled()"
|
|
||||||
(click)="_selectionCleared()"
|
|
||||||
size="icon-xs"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucideCircleX" />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button
|
|
||||||
hlmInputGroupButton
|
|
||||||
type="button"
|
|
||||||
tabindex="-1"
|
|
||||||
[attr.aria-label]="ariaLabelToggleButton()"
|
|
||||||
[disabled]="_disabled()"
|
|
||||||
(click)="_toggleOptions()"
|
|
||||||
size="icon-xs"
|
|
||||||
>
|
|
||||||
<ng-icon name="lucideChevronDown" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
*brnPopoverContent="let ctx"
|
|
||||||
hlmPopoverContent
|
|
||||||
class="p-0"
|
|
||||||
[style.width.px]="_elementRef.nativeElement.offsetWidth"
|
|
||||||
>
|
|
||||||
<hlm-autocomplete-list
|
|
||||||
[class]="_computedAutocompleteListClass()"
|
|
||||||
[class.hidden]="filteredOptions().length === 0"
|
|
||||||
>
|
|
||||||
<hlm-autocomplete-group>
|
|
||||||
@for (option of filteredOptions(); track option) {
|
|
||||||
<button
|
|
||||||
hlm-autocomplete-item
|
|
||||||
[class]="_computedAutocompleteItemClass()"
|
|
||||||
[value]="transformer ? transformer(option) : option"
|
|
||||||
(selected)="_optionSelected(option)"
|
|
||||||
>
|
|
||||||
@if (optionTemplate(); as optionTemplate) {
|
|
||||||
<ng-container
|
|
||||||
*ngTemplateOutlet="
|
|
||||||
optionTemplate;
|
|
||||||
context: { $implicit: option }
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
} @else {
|
|
||||||
{{ transformOptionToString()(option) }}
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</hlm-autocomplete-group>
|
|
||||||
</hlm-autocomplete-list>
|
|
||||||
|
|
||||||
<div
|
|
||||||
*brnAutocompleteEmpty
|
|
||||||
hlmAutocompleteEmpty
|
|
||||||
[class]="_computedAutocompleteEmptyClass()"
|
|
||||||
>
|
|
||||||
@if (loading()) {
|
|
||||||
<ng-content select="[loading]">{{ loadingText() }}</ng-content>
|
|
||||||
} @else {
|
|
||||||
<ng-content select="[empty]">{{ emptyText() }}</ng-content>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</brn-popover>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class HlmAutocomplete<T, V = T> implements ControlValueAccessor {
|
|
||||||
private static _id = 0;
|
|
||||||
private readonly _config = injectHlmAutocompleteConfig<T, V>();
|
|
||||||
|
|
||||||
private readonly _brnAutocomplete = viewChild.required(BrnAutocomplete);
|
|
||||||
|
|
||||||
private readonly _inputRef = viewChild.required('input', {
|
|
||||||
read: ElementRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
protected readonly _elementRef = inject(ElementRef<HTMLElement>);
|
|
||||||
|
|
||||||
/** The user defined class */
|
|
||||||
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
||||||
protected readonly _computedClass = computed(() =>
|
|
||||||
hlm('block w-full', this.userClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Custom class for the autocomplete search container. */
|
|
||||||
public readonly autocompleteSearchClass = input<ClassValue>('');
|
|
||||||
protected readonly _computedAutocompleteSearchClass = computed(() =>
|
|
||||||
hlm('', this.autocompleteSearchClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Custom class for the autocomplete input. */
|
|
||||||
public readonly autocompleteInputClass = input<ClassValue>('');
|
|
||||||
protected readonly _computedAutocompleteInputClass = computed(() =>
|
|
||||||
hlm('', this.autocompleteInputClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Custom class for the autocomplete list. */
|
|
||||||
public readonly autocompleteListClass = input<ClassValue>('');
|
|
||||||
protected readonly _computedAutocompleteListClass = computed(() =>
|
|
||||||
hlm('', this.autocompleteListClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Custom class for each autocomplete item. */
|
|
||||||
public readonly autocompleteItemClass = input<ClassValue>('');
|
|
||||||
protected readonly _computedAutocompleteItemClass = computed(() =>
|
|
||||||
hlm('', this.autocompleteItemClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Custom class for the empty and loading state container. */
|
|
||||||
public readonly autocompleteEmptyClass = input<ClassValue>('');
|
|
||||||
protected readonly _computedAutocompleteEmptyClass = computed(() =>
|
|
||||||
hlm('', this.autocompleteEmptyClass())
|
|
||||||
);
|
|
||||||
|
|
||||||
/** The list of filtered options to display in the autocomplete. */
|
|
||||||
public readonly filteredOptions = input<T[]>([]);
|
|
||||||
|
|
||||||
/** The selected value. */
|
|
||||||
public readonly value = model<T | V>();
|
|
||||||
|
|
||||||
/** Debounce time in milliseconds for the search input. */
|
|
||||||
public readonly debounceTime = input<number>(this._config.debounceTime);
|
|
||||||
|
|
||||||
/** The search query. */
|
|
||||||
public readonly search = model<string>('');
|
|
||||||
|
|
||||||
/** Debounced search query. */
|
|
||||||
protected readonly _search = debouncedSignal(
|
|
||||||
this.search,
|
|
||||||
this.debounceTime()
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Function to transform an option value to a search string. Defaults to identity function for strings. */
|
|
||||||
public readonly transformValueToSearch = input<(option: T) => string>(
|
|
||||||
this._config.transformValueToSearch
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Whether selection of an option is required. */
|
|
||||||
public readonly requireSelection = input<boolean, BooleanInput>(
|
|
||||||
this._config.requireSelection,
|
|
||||||
{
|
|
||||||
transform: booleanAttribute,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Function to transform an option value to a display string. Defaults to identity function for strings. */
|
|
||||||
public readonly transformOptionToString = input<(option: T) => string>(
|
|
||||||
this._config.transformOptionToString
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Function to transform the object to the value. */
|
|
||||||
public readonly transformOptionToValue = input<
|
|
||||||
((option: T) => V) | undefined
|
|
||||||
>(this._config.transformOptionToValue);
|
|
||||||
|
|
||||||
/** Function to display the selected value as a string. */
|
|
||||||
public readonly displayWith = input<((value: V) => string) | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Computed function to get the display value for the selected option. */
|
|
||||||
protected readonly _displaySearchValue = computed(() => {
|
|
||||||
const displayWith = this.displayWith();
|
|
||||||
if (displayWith) {
|
|
||||||
return displayWith;
|
|
||||||
} else {
|
|
||||||
return this.transformValueToSearch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Optional template for rendering each option. */
|
|
||||||
public readonly optionTemplate =
|
|
||||||
input<TemplateRef<HlmAutocompleteOption<T>>>();
|
|
||||||
|
|
||||||
/** Whether the autocomplete is in a loading state. */
|
|
||||||
public readonly loading = input<boolean, BooleanInput>(false, {
|
|
||||||
transform: booleanAttribute,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Whether to show the clear button when a option is selected. */
|
|
||||||
public readonly showClearBtn = input<boolean, BooleanInput>(
|
|
||||||
this._config.showClearBtn,
|
|
||||||
{
|
|
||||||
transform: booleanAttribute,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Placeholder text for the input field. */
|
|
||||||
public readonly searchPlaceholderText = input('Select an option');
|
|
||||||
|
|
||||||
/** Text to display when loading options. */
|
|
||||||
public readonly loadingText = input('Loading options...');
|
|
||||||
|
|
||||||
/** Text to display when no options are found. */
|
|
||||||
public readonly emptyText = input('No options found');
|
|
||||||
|
|
||||||
/** Aria label for the toggle button. */
|
|
||||||
public readonly ariaLabelToggleButton = input('Toggle options');
|
|
||||||
|
|
||||||
/** The id of the input field. */
|
|
||||||
public readonly inputId = input(
|
|
||||||
`hlm-autocomplete-input-${++HlmAutocomplete._id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Whether the autocomplete is disabled. */
|
|
||||||
public readonly disabled = input<boolean, BooleanInput>(false, {
|
|
||||||
transform: booleanAttribute,
|
|
||||||
});
|
|
||||||
|
|
||||||
protected readonly _disabled = linkedSignal(() => this.disabled());
|
|
||||||
|
|
||||||
/** Emitted when the selected value changes. */
|
|
||||||
public readonly valueChange = output<T | V | null>();
|
|
||||||
|
|
||||||
/** Emitted when the search query changes. */
|
|
||||||
public readonly searchChange = output<string>();
|
|
||||||
|
|
||||||
protected _onChange?: ChangeFn<T | V | null>;
|
|
||||||
protected _onTouched?: TouchFn;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
effect(() => {
|
|
||||||
const search = this._search();
|
|
||||||
this.searchChange.emit(search);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _searchChanged(event: Event) {
|
|
||||||
const value = (event.target as HTMLInputElement).value;
|
|
||||||
this.search.set(value ?? '');
|
|
||||||
|
|
||||||
if (!this._brnAutocomplete().isExpanded() && value.length > 0) {
|
|
||||||
this._brnAutocomplete().open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Toggle the options panel */
|
|
||||||
protected _toggleOptions() {
|
|
||||||
if (this._search() || this.filteredOptions().length > 0) {
|
|
||||||
// only toggle if there's a search term or options to show
|
|
||||||
this._brnAutocomplete().toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._inputRef().nativeElement.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clear the current selection and search input */
|
|
||||||
protected _selectionCleared() {
|
|
||||||
this.value.set(undefined);
|
|
||||||
this._onChange?.(null);
|
|
||||||
this.valueChange.emit(null);
|
|
||||||
this.search.set('');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _optionSelected(option: T) {
|
|
||||||
const transformer = this.transformOptionToValue();
|
|
||||||
|
|
||||||
const value = transformer ? transformer(option) : option;
|
|
||||||
|
|
||||||
this.value.set(value);
|
|
||||||
this._onChange?.(value);
|
|
||||||
this.valueChange.emit(value);
|
|
||||||
|
|
||||||
const searchValue = this._displaySearchValue()(value as any);
|
|
||||||
this.search.set(searchValue ?? '');
|
|
||||||
this._brnAutocomplete().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** CONTROL VALUE ACCESSOR */
|
|
||||||
public writeValue(value: T | V | null): void {
|
|
||||||
this.value.set(value ? value : undefined);
|
|
||||||
|
|
||||||
const searchValue = value ? this._displaySearchValue()(value as any) : '';
|
|
||||||
this.search.set(searchValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerOnChange(fn: ChangeFn<T | V | null>): void {
|
|
||||||
this._onChange = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerOnTouched(fn: TouchFn): void {
|
|
||||||
this._onTouched = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setDisabledState(isDisabled: boolean): void {
|
|
||||||
this._disabled.set(isDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _closed() {
|
|
||||||
if (this.requireSelection()) {
|
|
||||||
const value = this.value();
|
|
||||||
const searchValue = value ? this._displaySearchValue()(value as any) : '';
|
|
||||||
this.search.set(searchValue ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HlmAutocompleteOption<T> {
|
|
||||||
$implicit: T;
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { HlmBadge } from './lib/hlm-badge';
|
|
||||||
|
|
||||||
export * from './lib/hlm-badge';
|
|
||||||
|
|
||||||
export const HlmBadgeImports = [HlmBadge] as const;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue