Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 27 additions & 28 deletions pkg/controller/release_bearer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,12 @@ func (c *ReleaseBearerController) enqueueBearer(obj interface{}) {

key := bearer.Name

now := time.Now()
c.lastSeenMut.Lock()
c.lastSeen[key] = time.Now()
if bearer.CreationTimestamp.Time.Before(now) {
now = bearer.CreationTimestamp.Time
}
c.lastSeen[key] = now
c.lastSeenMut.Unlock()
c.workqueue.Add(key)
}
Expand Down Expand Up @@ -148,7 +152,6 @@ func (c *ReleaseBearerController) chunkHandler(ctx context.Context, name string)
c.lastSeenMut.RLock()
lastSeenTime, ok := c.lastSeen[name]
c.lastSeenMut.RUnlock()

if !ok {
return 0, nil
}
Expand Down Expand Up @@ -198,41 +201,37 @@ func (c *ReleaseBearerController) chunkHandler(ctx context.Context, name string)
return 10 * time.Second, fmt.Errorf("failed to delete bearer %s: %v", name, err)
}
case v1alpha1.BearerPhaseSucceeded:
ttl, ok := getTTLDuration(bearer.ObjectMeta, v1alpha1.ReleaseTTLAnnotation)

expiresIn := bearer.Status.TokenInfo.ExpiresIn
issuedAt := bearer.Status.TokenInfo.IssuedAt.Time
if expiresIn != 0 && !issuedAt.IsZero() {
expires := time.Duration(expiresIn) * time.Second
since := time.Since(issuedAt)

if ok {
ttl = min(ttl, expires-since)
} else {
ttl = expires - since
expirationTime := issuedAt.Add(expires)
if !expirationTime.After(time.Now()) {
klog.Infof("Deleting succeeded bearer %s after token expiration", name)
err = c.client.TaskV1alpha1().Bearers().Delete(ctx, bearer.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return 10 * time.Second, fmt.Errorf("failed to delete bearer %s: %v", name, err)
}
return 0, nil
}

sub := time.Since(lastSeenTime)
if sub < ttl {
return ttl - sub, nil
}
return time.Until(expirationTime) + 10*time.Second, nil
}

klog.Infof("Deleting succeeded bearer %s after token expiration", name)
err = c.client.TaskV1alpha1().Bearers().Delete(ctx, bearer.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return 10 * time.Second, fmt.Errorf("failed to delete bearer %s: %v", name, err)
}
} else if ok {
sub := time.Since(lastSeenTime)
if sub < ttl {
return ttl - sub, nil
}
ttl, ok := getTTLDuration(bearer.ObjectMeta, v1alpha1.ReleaseTTLAnnotation)
if !ok {
return 0, nil
}
sub := time.Since(lastSeenTime)
if sub < ttl {
return ttl - sub, nil
}

klog.Infof("Deleting succeeded bearer %s after %v", name, ttl)
err = c.client.TaskV1alpha1().Bearers().Delete(ctx, name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return 10 * time.Second, fmt.Errorf("failed to delete bearer %s: %v", name, err)
}
klog.Infof("Deleting succeeded bearer %s after %v", name, ttl)
err = c.client.TaskV1alpha1().Bearers().Delete(ctx, name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return 10 * time.Second, fmt.Errorf("failed to delete bearer %s: %v", name, err)
}
}
return 0, nil
Expand Down
7 changes: 5 additions & 2 deletions pkg/controller/release_blob_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,12 @@ func (c *ReleaseBlobController) enqueueBlob(obj interface{}) {

key := blob.Name

now := time.Now()
c.lastSeenMut.Lock()
c.lastSeen[key] = time.Now()
if blob.CreationTimestamp.Time.Before(now) {
now = blob.CreationTimestamp.Time
}
c.lastSeen[key] = now
c.lastSeenMut.Unlock()
c.workqueue.Add(key)
}
Expand Down Expand Up @@ -141,7 +145,6 @@ func (c *ReleaseBlobController) chunkHandler(ctx context.Context, name string) (
c.lastSeenMut.RLock()
lastSeenTime, ok := c.lastSeen[name]
c.lastSeenMut.RUnlock()

if !ok {
return 0, nil
}
Expand Down
7 changes: 5 additions & 2 deletions pkg/controller/release_chunk_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,12 @@ func (c *ReleaseChunkController) enqueueChunk(obj interface{}) {

key := chunk.Name

now := time.Now()
c.lastSeenMut.Lock()
c.lastSeen[key] = time.Now()
if chunk.CreationTimestamp.Time.Before(now) {
now = chunk.CreationTimestamp.Time
}
c.lastSeen[key] = now
c.lastSeenMut.Unlock()
c.workqueue.Add(key)
}
Expand Down Expand Up @@ -149,7 +153,6 @@ func (c *ReleaseChunkController) chunkHandler(ctx context.Context, name string)
c.lastSeenMut.RLock()
lastSeenTime, ok := c.lastSeen[name]
c.lastSeenMut.RUnlock()

if !ok {
return 0, nil
}
Expand Down
70 changes: 69 additions & 1 deletion pkg/webui/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,62 @@ <h2 class="status-title completed collapsed" onclick="toggleContainer('completed
</span>`;
}

const formatTimestamp = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now - date;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHr = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHr / 24);

if (diffSec < 60) {
return `${diffSec}s ago`;
} else if (diffMin < 60) {
return `${diffMin}m ago`;
} else if (diffHr < 24) {
return `${diffHr}h ago`;
} else if (diffDay < 7) {
return `${diffDay}d ago`;
} else {
return date.toLocaleString();
}
};

const formatDuration = (startTime, endTime) => {
if (!startTime || !endTime) return '';
const start = new Date(startTime);
const end = new Date(endTime);
const diffMs = end - start;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHr = Math.floor(diffMin / 60);

if (diffSec < 60) {
return `${diffSec}s`;
} else if (diffMin < 60) {
const sec = diffSec % 60;
return `${diffMin}m ${sec}s`;
} else {
const min = diffMin % 60;
return `${diffHr}h ${min}m`;
}
};

const highlightTime = (creationTime, completionTime, phase) => {
if (!creationTime) return '';

const created = `<span class="time-text" title="${creationTime}">Created: ${formatTimestamp(creationTime)}</span>`;

if (completionTime && (phase === 'Succeeded' || phase === 'Failed')) {
const duration = formatDuration(creationTime, completionTime);
return `<span title="Creation time, Completion time, Duration">${created} | Completed: ${formatTimestamp(completionTime)} | Duration: ${duration}</span>`;
}

return created;
};

const progressData = {};
const statusMap = {
'Pending': 'pending',
Expand Down Expand Up @@ -673,27 +729,39 @@ <h2 class="status-title completed collapsed" onclick="toggleContainer('completed
${highlightSpeed(speed, remainingTime)}
${hasChunks ? highlightChunksInfo(data) : `<span></span>`}
</div>
<div class="progress-container"><div class="progress-bar" style="width: ${progressPercent}%"></div></div>`;
<div class="progress-container"><div class="progress-bar" style="width: ${progressPercent}%"></div></div>
<div class="progress-text">
${highlightTime(data.creationTime, data.completionTime, data.phase)}
</div>`;
}
case 'Failed':
return `${header}
${highlightError(escapeErrors(errors))}
<div class="progress-text">
${size ? highlightProgress(progress, size) : `<span></span>`}
${hasChunks ? highlightChunksInfo(data) : `<span></span>`}
</div>
<div class="progress-text">
${highlightTime(data.creationTime, data.completionTime, data.phase)}
</div>`;
case 'Pending':
case 'Unknown':
return `${header}
<div class="progress-text">
${size ? highlightProgress(progress, size) : `<span></span>`}
${hasChunks ? highlightChunksInfo(data) : `<span></span>`}
</div>
<div class="progress-text">
${highlightTime(data.creationTime, data.completionTime, data.phase)}
</div>`;
case 'Succeeded':
return `${header}
<div class="progress-text">
${size ? highlightBytes(size) : `<span></span>`}
${hasChunks ? highlightChunksInfo(data) : `<span></span>`}
</div>
<div class="progress-text">
${highlightTime(data.creationTime, data.completionTime, data.phase)}
</div>`;
}
};
Expand Down
48 changes: 48 additions & 0 deletions pkg/webui/webui.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ type entry struct {

Errors []string `json:"errors,omitempty"`

CreationTime string `json:"creationTime,omitempty"`
CompletionTime string `json:"completionTime,omitempty"`

groupIgnoreSize bool `json:"-"`
}

Expand Down Expand Up @@ -519,6 +522,14 @@ func blobToEntry(blob *v1alpha1.Blob) *entry {
}
}

// Set timestamps
if !blob.CreationTimestamp.IsZero() {
e.CreationTime = blob.CreationTimestamp.Format(time.RFC3339)
}
if blob.Status.CompletionTime != nil && !blob.Status.CompletionTime.IsZero() {
e.CompletionTime = blob.Status.CompletionTime.Format(time.RFC3339)
}

return e
}

Expand Down Expand Up @@ -560,6 +571,15 @@ func chunkToEntry(chunk *v1alpha1.Chunk) *entry {
}
}
}

// Set timestamps
if !chunk.CreationTimestamp.IsZero() {
e.CreationTime = chunk.CreationTimestamp.Format(time.RFC3339)
}
if chunk.Status.CompletionTime != nil && !chunk.Status.CompletionTime.IsZero() {
e.CompletionTime = chunk.Status.CompletionTime.Format(time.RFC3339)
}

return e
}

Expand All @@ -582,6 +602,8 @@ func aggregateEntries(groupName string, entries map[string]*entry) *entry {
var hasFailed bool
var hasRunning bool
var hasPending bool
var earliestCreation time.Time
var latestCompletion time.Time

for uid, e := range entries {
// Add member info to the list
Expand All @@ -604,6 +626,24 @@ func aggregateEntries(groupName string, entries map[string]*entry) *entry {
maxPriority = e.Priority
}

// Track earliest creation time
if e.CreationTime != "" {
if t, err := time.Parse(time.RFC3339, e.CreationTime); err == nil {
if earliestCreation.IsZero() || t.Before(earliestCreation) {
earliestCreation = t
}
}
}

// Track latest completion time
if e.CompletionTime != "" {
if t, err := time.Parse(time.RFC3339, e.CompletionTime); err == nil {
if latestCompletion.IsZero() || t.After(latestCompletion) {
latestCompletion = t
}
}
}

if e.groupIgnoreSize {
continue
}
Expand Down Expand Up @@ -632,6 +672,14 @@ func aggregateEntries(groupName string, entries map[string]*entry) *entry {
aggregate.FailedChunks = failedChunks
aggregate.Priority = maxPriority

// Set timestamps
if !earliestCreation.IsZero() {
aggregate.CreationTime = earliestCreation.Format(time.RFC3339)
}
if !latestCompletion.IsZero() {
aggregate.CompletionTime = latestCompletion.Format(time.RFC3339)
}

completed := pendingChunks == 0 && runningChunks == 0 && idleChunks == 0 && !hasRunning && !hasPending

switch {
Expand Down
Loading