Compare commits
2 Commits
v1.1.0
...
fb27e9c026
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb27e9c026 | ||
|
|
14d2270260 |
7
backend/env/env.go
vendored
7
backend/env/env.go
vendored
@@ -17,12 +17,15 @@ const (
|
|||||||
PrivKey EnvKey = "PRIVKEY"
|
PrivKey EnvKey = "PRIVKEY"
|
||||||
Fullchain EnvKey = "FULLCHAIN"
|
Fullchain EnvKey = "FULLCHAIN"
|
||||||
Https EnvKey = "HTTPS"
|
Https EnvKey = "HTTPS"
|
||||||
Url EnvKey = "URL"
|
HostUrl EnvKey = "HOST_URL"
|
||||||
Port EnvKey = "PORT"
|
HostPort EnvKey = "HOST_PORT"
|
||||||
WorkingDir EnvKey = "WORKING_DIR"
|
WorkingDir EnvKey = "WORKING_DIR"
|
||||||
Spa EnvKey = "SPA"
|
Spa EnvKey = "SPA"
|
||||||
AccessSecret EnvKey = "ACCESS_SECRET"
|
AccessSecret EnvKey = "ACCESS_SECRET"
|
||||||
RefreshSecret EnvKey = "REFRESH_SECRET"
|
RefreshSecret EnvKey = "REFRESH_SECRET"
|
||||||
|
Organization EnvKey = "ORGANIZATION"
|
||||||
|
DOMAIN EnvKey = "DOMAIN"
|
||||||
|
AllowOrigin EnvKey = "ALLOWORIGIN"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ GIN_MODE=release
|
|||||||
DEBUG=false
|
DEBUG=false
|
||||||
SPA=directory_of_spa_files
|
SPA=directory_of_spa_files
|
||||||
WORKING_DIR=.
|
WORKING_DIR=.
|
||||||
URL=your_local_url
|
HOST_URL=your_local_url
|
||||||
PORT=your_local_port
|
HOST_PORT=your_local_port
|
||||||
HTTPS=true
|
HTTPS=true
|
||||||
PRIVKEY=your_certificate_key_file
|
PRIVKEY=your_certificate_key_file
|
||||||
FULLCHAIN=your_certificate_fullchain_file
|
FULLCHAIN=your_certificate_fullchain_file
|
||||||
ACCESS_SECRET=your_32bit_long_access_secret
|
ACCESS_SECRET=your_32bit_long_access_secret
|
||||||
REFRESH_SECRET=your_32bit_long_referesh_secret
|
REFRESH_SECRET=your_32bit_long_referesh_secret
|
||||||
|
ALLOWORIGIN=all_allowed_urls
|
||||||
|
DOMAIN=your_domain
|
||||||
@@ -3,7 +3,7 @@ module backend
|
|||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.tecamino.com/paadi/access-handler v1.0.22
|
gitea.tecamino.com/paadi/access-handler v1.0.24
|
||||||
gitea.tecamino.com/paadi/memberDB v1.1.2
|
gitea.tecamino.com/paadi/memberDB v1.1.2
|
||||||
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
|
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
|
||||||
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
|
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
gitea.tecamino.com/paadi/access-handler v1.0.22 h1:XFi2PQ1gWqe9YuSye4Ti1o5TpdV0AFpt4Fb58MFMagk=
|
gitea.tecamino.com/paadi/access-handler v1.0.24 h1:wgCMJg1tgtPSCCVWLyNx1bT3B1N2NwHth0UJAH2nZIY=
|
||||||
gitea.tecamino.com/paadi/access-handler v1.0.22/go.mod h1:wKsB5/Rvaj580gdg3+GbUf5V/0N00XN6cID+C/8135M=
|
gitea.tecamino.com/paadi/access-handler v1.0.24/go.mod h1:wKsB5/Rvaj580gdg3+GbUf5V/0N00XN6cID+C/8135M=
|
||||||
gitea.tecamino.com/paadi/dbHandler v1.0.8 h1:ZWSBM/KFtLwTv2cBqwK1mOxWAxAfL0BcWEC3kJ9JALU=
|
gitea.tecamino.com/paadi/dbHandler v1.0.8 h1:ZWSBM/KFtLwTv2cBqwK1mOxWAxAfL0BcWEC3kJ9JALU=
|
||||||
gitea.tecamino.com/paadi/dbHandler v1.0.8/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
|
gitea.tecamino.com/paadi/dbHandler v1.0.8/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
|
||||||
gitea.tecamino.com/paadi/memberDB v1.1.2 h1:j/Tsr7JnzAkdOvgjG77TzTVBWd4vBrmEFzPXNpW7GYk=
|
gitea.tecamino.com/paadi/memberDB v1.1.2 h1:j/Tsr7JnzAkdOvgjG77TzTVBWd4vBrmEFzPXNpW7GYk=
|
||||||
|
|||||||
@@ -24,12 +24,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var allowOrigins models.StringSlice
|
// set cli flage
|
||||||
|
|
||||||
flag.Var(&allowOrigins, "allowOrigin", "Allowed origin (can repeat this flag)")
|
|
||||||
|
|
||||||
envFile := flag.String("env", ".env", "enviroment file")
|
envFile := flag.String("env", ".env", "enviroment file")
|
||||||
organization := flag.String("organization", "", "self signed ciertificate organization")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// load enviroment file if exists
|
// load enviroment file if exists
|
||||||
@@ -51,6 +47,16 @@ func main() {
|
|||||||
os.Chdir(workingDir)
|
os.Chdir(workingDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set allowed origins
|
||||||
|
var allowOrigins models.StringSlice
|
||||||
|
|
||||||
|
if strings.Contains(env.DOMAIN.GetValue(), "http") {
|
||||||
|
allowOrigins.Set(env.DOMAIN.GetValue())
|
||||||
|
}
|
||||||
|
if env.AllowOrigin.GetValue() != "" {
|
||||||
|
allowOrigins.Set(env.AllowOrigin.GetValue())
|
||||||
|
}
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not get working directory: %v", err)
|
log.Fatalf("Could not get working directory: %v", err)
|
||||||
@@ -103,6 +109,8 @@ func main() {
|
|||||||
allowOrigins = append(allowOrigins, fmt.Sprintf("%s%s:9000", httpString, localIP), fmt.Sprintf("%s%s:9500", httpString, localIP))
|
allowOrigins = append(allowOrigins, fmt.Sprintf("%s%s:9000", httpString, localIP), fmt.Sprintf("%s%s:9500", httpString, localIP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(100, allowOrigins)
|
||||||
|
|
||||||
s.Routes.Use(cors.New(cors.Config{
|
s.Routes.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: allowOrigins,
|
AllowOrigins: allowOrigins,
|
||||||
//AllowOrigins: []string{"*"},
|
//AllowOrigins: []string{"*"},
|
||||||
@@ -181,7 +189,7 @@ func main() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
if err := utils.OpenBrowser(fmt.Sprintf("%slocalhost:%s", httpString, env.Port.GetValue()), logger); err != nil {
|
if err := utils.OpenBrowser(fmt.Sprintf("%slocalhost:%s", httpString, env.HostPort.GetValue()), logger); err != nil {
|
||||||
logger.Error("main", fmt.Sprintf("starting browser error : %s", err))
|
logger.Error("main", fmt.Sprintf("starting browser error : %s", err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -197,16 +205,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start https server
|
// start https server
|
||||||
logger.Info("main", fmt.Sprintf("https listen on ip: %s port: %s", env.Url.GetValue(), env.Port.GetValue()))
|
logger.Info("main", fmt.Sprintf("https listen on ip: %s port: %s", env.HostUrl.GetValue(), env.HostPort.GetValue()))
|
||||||
if err := s.ServeHttps(env.Url.GetValue(), env.Port.GetUIntValue(), cert.Cert{Organization: *organization, CertFile: env.Fullchain.GetValue(), KeyFile: env.PrivKey.GetValue()}); err != nil {
|
if err := s.ServeHttps(env.HostUrl.GetValue(), env.HostPort.GetUIntValue(), cert.Cert{Organization: env.Organization.GetValue(), CertFile: env.Fullchain.GetValue(), KeyFile: env.PrivKey.GetValue()}); err != nil {
|
||||||
logger.Error("main", "error https server "+err.Error())
|
logger.Error("main", "error https server "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// start http server
|
// start http server
|
||||||
logger.Info("main", fmt.Sprintf("http listen on ip: %s port: %s", env.Url.GetValue(), env.Port.GetValue()))
|
logger.Info("main", fmt.Sprintf("http listen on ip: %s port: %s", env.HostUrl.GetValue(), env.HostPort.GetValue()))
|
||||||
if err := s.ServeHttp(env.Url.GetValue(), env.Port.GetUIntValue()); err != nil {
|
if err := s.ServeHttp(env.HostUrl.GetValue(), env.HostPort.GetUIntValue()); err != nil {
|
||||||
logger.Error("main", "error http server "+err.Error())
|
logger.Error("main", "error http server "+err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import "strings"
|
|||||||
|
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
|
|
||||||
func (s *StringSlice) String() string {
|
func (s *StringSlice) Set(value string) {
|
||||||
return strings.Join(*s, ",")
|
if strings.Contains(value, ",") {
|
||||||
|
*s = append(*s, strings.Split(value, ",")...)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StringSlice) Set(value string) error {
|
|
||||||
*s = append(*s, value)
|
*s = append(*s, value)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ lastVisit: Letscht Bsuech
|
|||||||
search: Suechi
|
search: Suechi
|
||||||
noDataAvailable: Keni Date
|
noDataAvailable: Keni Date
|
||||||
importCSV: importier CSV
|
importCSV: importier CSV
|
||||||
|
exportCSV: exportier CSV
|
||||||
selectMemberOptions: Wähle Mitglieder Optione
|
selectMemberOptions: Wähle Mitglieder Optione
|
||||||
addNewMember: Neues Mitglied
|
addNewMember: Neues Mitglied
|
||||||
csvOptions: CSV Optionen
|
csvOptions: CSV Optionen
|
||||||
@@ -118,3 +119,5 @@ responsibles: Verantwortliche
|
|||||||
comment: Bemerkung
|
comment: Bemerkung
|
||||||
dark_mode: Dunkel-Modus
|
dark_mode: Dunkel-Modus
|
||||||
light_mode: Hell-Modus
|
light_mode: Hell-Modus
|
||||||
|
import: Import
|
||||||
|
export: Export
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ lastVisit: Letzter Besuch
|
|||||||
search: Suche
|
search: Suche
|
||||||
noDataAvailable: Keine Daten
|
noDataAvailable: Keine Daten
|
||||||
importCSV: importiere CSV
|
importCSV: importiere CSV
|
||||||
|
exportCSV: exportiere CSV
|
||||||
selectMemberOptions: Wähle Mitglieder Optionen
|
selectMemberOptions: Wähle Mitglieder Optionen
|
||||||
addNewMember: Neues Mitglied
|
addNewMember: Neues Mitglied
|
||||||
csvOptions: CSV Optionen
|
csvOptions: CSV Optionen
|
||||||
@@ -118,3 +119,5 @@ responsibles: Verantwortliche
|
|||||||
comment: Bemerkung
|
comment: Bemerkung
|
||||||
dark_mode: Dunkel-Modus
|
dark_mode: Dunkel-Modus
|
||||||
light_mode: Hell-Modus
|
light_mode: Hell-Modus
|
||||||
|
import: Import
|
||||||
|
export: Export
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ lastVisit: Last Visit
|
|||||||
search: search
|
search: search
|
||||||
noDataAvailable: No data available
|
noDataAvailable: No data available
|
||||||
importCSV: Import CSV
|
importCSV: Import CSV
|
||||||
|
exportCSV: Export CSV
|
||||||
selectMemberOptions: Select Member Options
|
selectMemberOptions: Select Member Options
|
||||||
addNewMember: Add new Member
|
addNewMember: Add new Member
|
||||||
csvOptions: CSV Options
|
csvOptions: CSV Options
|
||||||
@@ -118,3 +119,5 @@ responsibles: Responsibles
|
|||||||
comment: Comment
|
comment: Comment
|
||||||
dark_mode: Dark-Mode
|
dark_mode: Dark-Mode
|
||||||
light_mode: Light-Mode
|
light_mode: Light-Mode
|
||||||
|
import: Import
|
||||||
|
export: Export
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
ref="dialog"
|
ref="dialog"
|
||||||
:header-title="newRole ? $t('addNewRole') : 'Edit ' + localRole.role"
|
:header-title="newRole ? $t('addNewRole') : 'Edit ' + localRole.role"
|
||||||
:height="700"
|
:height="700"
|
||||||
:width="500"
|
:width="700"
|
||||||
>
|
>
|
||||||
<div class="row justify-center">
|
<div class="row justify-center">
|
||||||
<q-input
|
<q-input
|
||||||
|
|||||||
@@ -24,6 +24,20 @@
|
|||||||
@update:model-value="(val) => toggleBit(index, 2, val)"
|
@update:model-value="(val) => toggleBit(index, 2, val)"
|
||||||
>{{ i18n.global.t('delete') }}</q-checkbox
|
>{{ i18n.global.t('delete') }}</q-checkbox
|
||||||
>
|
>
|
||||||
|
<q-checkbox
|
||||||
|
v-if="permission.permissionNumber > 3"
|
||||||
|
class="q-mx-md"
|
||||||
|
:model-value="isFlagSet(permission.permission, 1 << 3)"
|
||||||
|
@update:model-value="(val) => toggleBit(index, 3, val)"
|
||||||
|
>{{ i18n.global.t('import') }}</q-checkbox
|
||||||
|
>
|
||||||
|
<q-checkbox
|
||||||
|
v-if="permission.permissionNumber > 4"
|
||||||
|
class="q-mx-md"
|
||||||
|
:model-value="isFlagSet(permission.permission, 1 << 4)"
|
||||||
|
@update:model-value="(val) => toggleBit(index, 4, val)"
|
||||||
|
>{{ i18n.global.t('export') }}</q-checkbox
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-card>
|
</q-card>
|
||||||
@@ -47,6 +61,7 @@ const localPermission = ref(
|
|||||||
props.permissions.map((e) => ({
|
props.permissions.map((e) => ({
|
||||||
name: e.name,
|
name: e.name,
|
||||||
permission: e.permission ?? 0,
|
permission: e.permission ?? 0,
|
||||||
|
permissionNumber: e.name === 'members' ? 5 : 3,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
isPermittedTo: (state: UserState) => {
|
isPermittedTo: (state: UserState) => {
|
||||||
return (name: string, type: 'read' | 'write' | 'delete'): boolean => {
|
return (name: string, type: 'read' | 'write' | 'delete' | 'import' | 'export'): boolean => {
|
||||||
const permission = state.user?.permissions?.find((r: Permission) => r.name === name);
|
const permission = state.user?.permissions?.find((r: Permission) => r.name === name);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'read':
|
case 'read':
|
||||||
@@ -33,6 +33,10 @@ export const useUserStore = defineStore('user', {
|
|||||||
return permission?.permission ? (permission.permission & (1 << 1)) === 2 : false;
|
return permission?.permission ? (permission.permission & (1 << 1)) === 2 : false;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return permission?.permission ? (permission.permission & (1 << 2)) === 4 : false;
|
return permission?.permission ? (permission.permission & (1 << 2)) === 4 : false;
|
||||||
|
case 'import':
|
||||||
|
return permission?.permission ? (permission.permission & (1 << 3)) === 8 : false;
|
||||||
|
case 'export':
|
||||||
|
return permission?.permission ? (permission.permission & (1 << 4)) === 16 : false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Member, Members } from 'src/vueLib/models/member';
|
|||||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||||
import { i18n } from 'boot/lang';
|
import { i18n } from 'boot/lang';
|
||||||
import { useResponsibleTable } from '../responsible/ResponsibleTable';
|
import { useResponsibleTable } from '../responsible/ResponsibleTable';
|
||||||
|
import { appName } from 'src/vueLib/models/settings';
|
||||||
|
|
||||||
export function useMemberTable() {
|
export function useMemberTable() {
|
||||||
const members = ref<Members>([]);
|
const members = ref<Members>([]);
|
||||||
@@ -253,6 +254,47 @@ export function useMemberTable() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportCsv() {
|
||||||
|
const comma = ';';
|
||||||
|
// Extract only columns that have a field (not icons/options)
|
||||||
|
const exportableColumns = columns.value.filter(
|
||||||
|
(col) => typeof col.field === 'string' && col.field !== 'cake' && col.field !== 'option',
|
||||||
|
) as { field: keyof Member; label: string }[];
|
||||||
|
|
||||||
|
// Build CSV header row
|
||||||
|
const header = exportableColumns.map((col) => col.field).join(comma);
|
||||||
|
|
||||||
|
// Build CSV rows
|
||||||
|
const data = members.value.map((member) =>
|
||||||
|
exportableColumns
|
||||||
|
.map((col) => {
|
||||||
|
const value = member[col.field];
|
||||||
|
// handle nested objects (e.g. responsiblePerson)
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if ('firstName' in value && 'lastName' in value)
|
||||||
|
return `"${value.firstName} ${value.lastName}"`;
|
||||||
|
return `"${JSON.stringify(value)}"`;
|
||||||
|
}
|
||||||
|
return `"${value ?? ''}"`;
|
||||||
|
})
|
||||||
|
.join(comma),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine into CSV string
|
||||||
|
const csv = [header, ...data].join('\n');
|
||||||
|
|
||||||
|
// Create blob and trigger download
|
||||||
|
const BOM = '\uFEFF';
|
||||||
|
const blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', i18n.global.t(appName.value) + '.csv');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members,
|
members,
|
||||||
responsibles,
|
responsibles,
|
||||||
@@ -263,5 +305,6 @@ export function useMemberTable() {
|
|||||||
updateMembers,
|
updateMembers,
|
||||||
isXDaysBeforeAnnualDate,
|
isXDaysBeforeAnnualDate,
|
||||||
disableColumns,
|
disableColumns,
|
||||||
|
exportCsv,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<q-tooltip>{{ $t('selectMemberOptions') }}</q-tooltip>
|
<q-tooltip>{{ $t('selectMemberOptions') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="user.isPermittedTo('members', 'write')"
|
v-if="user.isPermittedTo('members', 'import')"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
icon="upload"
|
icon="upload"
|
||||||
@@ -51,6 +51,15 @@
|
|||||||
>
|
>
|
||||||
<q-tooltip>{{ $t('importCSV') }}</q-tooltip>
|
<q-tooltip>{{ $t('importCSV') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="user.isPermittedTo('members', 'export')"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="download"
|
||||||
|
@click="exportCsv"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ $t('exportCSV') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
</q-btn-group>
|
</q-btn-group>
|
||||||
<div v-if="selectOption && selected.length > 0">
|
<div v-if="selectOption && selected.length > 0">
|
||||||
<q-btn
|
<q-btn
|
||||||
@@ -240,6 +249,7 @@ const {
|
|||||||
updateMembers,
|
updateMembers,
|
||||||
isXDaysBeforeAnnualDate,
|
isXDaysBeforeAnnualDate,
|
||||||
disableColumns,
|
disableColumns,
|
||||||
|
exportCsv,
|
||||||
} = useMemberTable();
|
} = useMemberTable();
|
||||||
|
|
||||||
//load on mounting page
|
//load on mounting page
|
||||||
|
|||||||
Reference in New Issue
Block a user