mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
commit
d0516f12b0
@ -4,7 +4,7 @@ before:
|
|||||||
- go mod tidy
|
- go mod tidy
|
||||||
builds:
|
builds:
|
||||||
- env:
|
- env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=1
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
@ -12,7 +12,6 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
goamd64:
|
goamd64:
|
||||||
- v2
|
|
||||||
- v3
|
- v3
|
||||||
goarm:
|
goarm:
|
||||||
- "7"
|
- "7"
|
||||||
@ -20,6 +19,11 @@ builds:
|
|||||||
main: ./cmd/cc-backend
|
main: ./cmd/cc-backend
|
||||||
tags:
|
tags:
|
||||||
- static_build
|
- static_build
|
||||||
|
hooks:
|
||||||
|
pre: make frontend
|
||||||
|
ignore:
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- format: tar.gz
|
- format: tar.gz
|
||||||
|
6
Makefile
6
Makefile
@ -28,7 +28,7 @@ SVELTE_SRC = $(wildcard $(FRONTEND)/src/*.svelte) \
|
|||||||
$(wildcard $(FRONTEND)/src/plots/*.svelte) \
|
$(wildcard $(FRONTEND)/src/plots/*.svelte) \
|
||||||
$(wildcard $(FRONTEND)/src/joblist/*.svelte)
|
$(wildcard $(FRONTEND)/src/joblist/*.svelte)
|
||||||
|
|
||||||
.PHONY: clean test tags $(TARGET)
|
.PHONY: clean test tags frontend $(TARGET)
|
||||||
|
|
||||||
.NOTPARALLEL:
|
.NOTPARALLEL:
|
||||||
|
|
||||||
@ -36,6 +36,10 @@ $(TARGET): $(VAR) $(CFG) $(SVELTE_TARGETS)
|
|||||||
$(info ===> BUILD cc-backend)
|
$(info ===> BUILD cc-backend)
|
||||||
@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend
|
@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
$(info ===> BUILD frontend)
|
||||||
|
cd web/frontend && npm install && npm run build
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(info ===> CLEAN)
|
$(info ===> CLEAN)
|
||||||
@go clean
|
@go clean
|
||||||
|
@ -192,6 +192,7 @@ func decode(r io.Reader, val interface{}) error {
|
|||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/ [get]
|
// @router /jobs/ [get]
|
||||||
func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) {
|
if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) {
|
||||||
handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw)
|
handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw)
|
||||||
return
|
return
|
||||||
|
@ -81,8 +81,7 @@ func (r *JobRepository) testQueryJobs(
|
|||||||
page *model.PageRequest,
|
page *model.PageRequest,
|
||||||
order *model.OrderByInput) ([]*schema.Job, error) {
|
order *model.OrderByInput) ([]*schema.Job, error) {
|
||||||
|
|
||||||
return r.queryJobs(sq.Select(jobColumns...).From("job"),
|
return r.queryJobs(sq.Select(jobColumns...).From("job"), filters, page, order)
|
||||||
filters, page, order)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public function with added securityCheck, calls private queryJobs function above
|
// Public function with added securityCheck, calls private queryJobs function above
|
||||||
@ -98,8 +97,7 @@ func (r *JobRepository) QueryJobs(
|
|||||||
return nil, qerr
|
return nil, qerr
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.queryJobs(query,
|
return r.queryJobs(query, filters, page, order)
|
||||||
filters, page, order)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityCheck-less, private: returns a list of minimal job information (DB-ID and jobId) of shared jobs for link-building based the provided filters.
|
// SecurityCheck-less, private: returns a list of minimal job information (DB-ID and jobId) of shared jobs for link-building based the provided filters.
|
||||||
@ -202,12 +200,12 @@ func (r *JobRepository) CountJobs(
|
|||||||
return r.countJobs(query, filters)
|
return r.countJobs(query, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (queryOut sq.SelectBuilder, err error) {
|
func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {
|
||||||
user := auth.GetUser(ctx)
|
user := auth.GetUser(ctx)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
var qnil sq.SelectBuilder
|
var qnil sq.SelectBuilder
|
||||||
return qnil, fmt.Errorf("user context is nil!")
|
return qnil, fmt.Errorf("user context is nil!")
|
||||||
} else if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport}) { // Admin & Co. : All jobs
|
} else if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleApi}) { // Admin & Co. : All jobs
|
||||||
return query, nil
|
return query, nil
|
||||||
} else if user.HasRole(auth.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs
|
} else if user.HasRole(auth.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs
|
||||||
if len(user.Projects) != 0 {
|
if len(user.Projects) != 0 {
|
||||||
|
@ -24,6 +24,16 @@
|
|||||||
export let type;
|
export let type;
|
||||||
export let filterPresets;
|
export let filterPresets;
|
||||||
|
|
||||||
|
// By default, look at the jobs of the last 30 days:
|
||||||
|
if (filterPresets?.startTime == null) {
|
||||||
|
if (filterPresets == null)
|
||||||
|
filterPresets = {}
|
||||||
|
|
||||||
|
const lastMonth = (new Date(Date.now() - (30*24*60*60*1000))).toISOString()
|
||||||
|
const now = (new Date(Date.now())).toISOString()
|
||||||
|
filterPresets.startTime = { from: lastMonth, to: now, text: 'Last 30 Days', url: 'last30d' }
|
||||||
|
}
|
||||||
|
|
||||||
console.assert(
|
console.assert(
|
||||||
type == "USER" || type == "PROJECT",
|
type == "USER" || type == "PROJECT",
|
||||||
"Invalid list type provided!"
|
"Invalid list type provided!"
|
||||||
|
@ -35,17 +35,17 @@
|
|||||||
projectMatch: filterPresets.projectMatch || 'contains',
|
projectMatch: filterPresets.projectMatch || 'contains',
|
||||||
userMatch: filterPresets.userMatch || 'contains',
|
userMatch: filterPresets.userMatch || 'contains',
|
||||||
|
|
||||||
cluster: filterPresets.cluster || null,
|
cluster: filterPresets.cluster || null,
|
||||||
partition: filterPresets.partition || null,
|
partition: filterPresets.partition || null,
|
||||||
states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates,
|
states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates,
|
||||||
startTime: filterPresets.startTime || { from: null, to: null },
|
startTime: filterPresets.startTime || { from: null, to: null },
|
||||||
tags: filterPresets.tags || [],
|
tags: filterPresets.tags || [],
|
||||||
duration: filterPresets.duration || { from: null, to: null },
|
duration: filterPresets.duration || { from: null, to: null },
|
||||||
jobId: filterPresets.jobId || '',
|
jobId: filterPresets.jobId || '',
|
||||||
arrayJobId: filterPresets.arrayJobId || null,
|
arrayJobId: filterPresets.arrayJobId || null,
|
||||||
user: filterPresets.user || '',
|
user: filterPresets.user || '',
|
||||||
project: filterPresets.project || '',
|
project: filterPresets.project || '',
|
||||||
jobName: filterPresets.jobName || '',
|
jobName: filterPresets.jobName || '',
|
||||||
|
|
||||||
numNodes: filterPresets.numNodes || { from: null, to: null },
|
numNodes: filterPresets.numNodes || { from: null, to: null },
|
||||||
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
|
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
|
||||||
@ -120,7 +120,11 @@
|
|||||||
for (let state of filters.states)
|
for (let state of filters.states)
|
||||||
opts.push(`state=${state}`)
|
opts.push(`state=${state}`)
|
||||||
if (filters.startTime.from && filters.startTime.to)
|
if (filters.startTime.from && filters.startTime.to)
|
||||||
opts.push(`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`)
|
// if (filters.startTime.url) {
|
||||||
|
// opts.push(`startTime=${filters.startTime.url}`)
|
||||||
|
// } else {
|
||||||
|
opts.push(`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`)
|
||||||
|
// }
|
||||||
for (let tag of filters.tags)
|
for (let tag of filters.tags)
|
||||||
opts.push(`tag=${tag}`)
|
opts.push(`tag=${tag}`)
|
||||||
if (filters.duration.from && filters.duration.to)
|
if (filters.duration.from && filters.duration.to)
|
||||||
@ -193,16 +197,18 @@
|
|||||||
<DropdownItem divider/>
|
<DropdownItem divider/>
|
||||||
<DropdownItem disabled>Start Time Qick Selection</DropdownItem>
|
<DropdownItem disabled>Start Time Qick Selection</DropdownItem>
|
||||||
{#each [
|
{#each [
|
||||||
{ text: 'Last 6hrs', seconds: 6*60*60 },
|
{ text: 'Last 6hrs', url: 'last6h', seconds: 6*60*60 },
|
||||||
{ text: 'Last 12hrs', seconds: 12*60*60 },
|
// { text: 'Last 12hrs', seconds: 12*60*60 },
|
||||||
{ text: 'Last 24hrs', seconds: 24*60*60 },
|
{ text: 'Last 24hrs', url: 'last24h', seconds: 24*60*60 },
|
||||||
{ text: 'Last 48hrs', seconds: 48*60*60 },
|
// { text: 'Last 48hrs', seconds: 48*60*60 },
|
||||||
{ text: 'Last 7 days', seconds: 7*24*60*60 },
|
{ text: 'Last 7 days', url: 'last7d', seconds: 7*24*60*60 },
|
||||||
{ text: 'Last 30 days', seconds: 30*24*60*60 }
|
{ text: 'Last 30 days', url: 'last30d', seconds: 30*24*60*60 }
|
||||||
] as {text, seconds}}
|
] as {text, url, seconds}}
|
||||||
<DropdownItem on:click={() => {
|
<DropdownItem on:click={() => {
|
||||||
filters.startTime.from = (new Date(Date.now() - seconds * 1000)).toISOString()
|
filters.startTime.from = (new Date(Date.now() - seconds * 1000)).toISOString()
|
||||||
filters.startTime.to = (new Date(Date.now())).toISOString()
|
filters.startTime.to = (new Date(Date.now())).toISOString()
|
||||||
|
filters.startTime.text = text,
|
||||||
|
filters.startTime.url = url
|
||||||
update()
|
update()
|
||||||
}}>
|
}}>
|
||||||
<Icon name="calendar-range"/> {text}
|
<Icon name="calendar-range"/> {text}
|
||||||
@ -212,27 +218,6 @@
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ButtonDropdown>
|
</ButtonDropdown>
|
||||||
</Col>
|
</Col>
|
||||||
<!-- {#if startTimeQuickSelect}
|
|
||||||
<Col xs="auto">
|
|
||||||
<TimeSelection customEnabled={false} anyEnabled={true}
|
|
||||||
from={filters.startTime.from ? new Date(filters.startTime.from) : null}
|
|
||||||
to={filters.startTime.to ? new Date(filters.startTime.to) : null}
|
|
||||||
options={{
|
|
||||||
'Last 6hrs': 6*60*60,
|
|
||||||
'Last 12hrs': 12*60*60,
|
|
||||||
'Last 24hrs': 24*60*60,
|
|
||||||
'Last 48hrs': 48*60*60,
|
|
||||||
'Last 7 days': 7*24*60*60,
|
|
||||||
'Last 30 days': 30*24*60*60}}
|
|
||||||
on:change={({ detail: { from, to } }) => {
|
|
||||||
filters.startTime.from = from?.toISOString()
|
|
||||||
filters.startTime.to = to?.toISOString()
|
|
||||||
// console.log(filters.startTime)
|
|
||||||
update()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
{/if} -->
|
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
{#if filters.cluster}
|
{#if filters.cluster}
|
||||||
<Info icon="cpu" on:click={() => (isClusterOpen = true)}>
|
<Info icon="cpu" on:click={() => (isClusterOpen = true)}>
|
||||||
@ -251,8 +236,12 @@
|
|||||||
|
|
||||||
{#if filters.startTime.from || filters.startTime.to}
|
{#if filters.startTime.from || filters.startTime.to}
|
||||||
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
||||||
{new Date(filters.startTime.from).toLocaleString()} - {new Date(filters.startTime.to).toLocaleString()}
|
{#if filters.startTime.text}
|
||||||
</Info>
|
{filters.startTime.text}
|
||||||
|
{:else}
|
||||||
|
{new Date(filters.startTime.from).toLocaleString()} - {new Date(filters.startTime.to).toLocaleString()}
|
||||||
|
{/if}
|
||||||
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.duration.from || filters.duration.to}
|
{#if filters.duration.from || filters.duration.to}
|
||||||
@ -307,7 +296,11 @@
|
|||||||
bind:isOpen={isStartTimeOpen}
|
bind:isOpen={isStartTimeOpen}
|
||||||
bind:from={filters.startTime.from}
|
bind:from={filters.startTime.from}
|
||||||
bind:to={filters.startTime.to}
|
bind:to={filters.startTime.to}
|
||||||
on:update={() => update()} />
|
on:update={() => {
|
||||||
|
delete filters.startTime['text']
|
||||||
|
delete filters.startTime['url']
|
||||||
|
update()
|
||||||
|
}} />
|
||||||
|
|
||||||
<Duration
|
<Duration
|
||||||
bind:isOpen={isDurationOpen}
|
bind:isOpen={isDurationOpen}
|
||||||
|
Loading…
Reference in New Issue
Block a user