diff --git a/main/http_server/axe-os/src/app/components/edit/edit.component.html b/main/http_server/axe-os/src/app/components/edit/edit.component.html
index 6b178fbdd0..1b9a6be8e0 100644
--- a/main/http_server/axe-os/src/app/components/edit/edit.component.html
+++ b/main/http_server/axe-os/src/app/components/edit/edit.component.html
@@ -100,6 +100,37 @@
+
Presets
+
+
+
+
+
+
+
+
+
+
+
+
Captures the current Frequency and Core Voltage above. Max {{ maxPresets }} presets.
+
+
+ {{ p.name }} — {{ p.frequency }} MHz / {{ p.coreVoltage }} mV
+
+
+
+
+
+
+
+
+
+
+
+
Display
diff --git a/main/http_server/axe-os/src/app/components/edit/edit.component.ts b/main/http_server/axe-os/src/app/components/edit/edit.component.ts
index 9982e9bd29..cd4ead228b 100644
--- a/main/http_server/axe-os/src/app/components/edit/edit.component.ts
+++ b/main/http_server/axe-os/src/app/components/edit/edit.component.ts
@@ -16,12 +16,22 @@ import { SliderModule } from 'primeng/slider';
import { TooltipModule } from 'primeng/tooltip';
import { InputTextModule } from 'primeng/inputtext';
import { DateAgoPipe } from 'src/app/pipes/date-ago.pipe';
+import { LocalStorageService } from 'src/app/local-storage.service';
type SelectOption = {
name: string;
value: number;
};
+type Preset = {
+ name: string;
+ frequency: number;
+ coreVoltage: number;
+};
+
+const PRESETS_KEY = 'ASIC_PRESETS';
+const MAX_PRESETS = 5;
+
const DISPLAY_TIMEOUT_STEPS = [0, 1, 2, 5, 15, 30, 60, 60 * 2, 60 * 4, 60* 8, -1];
const STATS_FREQUENCY_STEPS = [0, 1, 2, 5, 10, 30, 60, 60 * 2, 60 * 6, 60 * 14, 60 * 28, 60 * 60];
@@ -68,6 +78,11 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges {
public statsFrequencyControl: FormControl;
public statsLimit: number = 720;
+ public presets: Preset[] = [];
+ public presetName = new FormControl('');
+ public editingIndex: number | null = null;
+ public readonly maxPresets = MAX_PRESETS;
+
constructor(
private fb: FormBuilder,
private systemService: SystemApiService,
@@ -75,6 +90,7 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges {
private toastr: ToastrService,
private loadingService: LoadingService,
private route: ActivatedRoute,
+ private localStorageService: LocalStorageService,
) {
// Check URL parameter for settings unlock
this.route.queryParams.subscribe(params => {
@@ -136,6 +152,7 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges {
ngOnInit(): void {
this.loadDeviceSettings();
+ this.loadPresets();
}
ngOnChanges(changes: SimpleChanges): void {
@@ -271,6 +288,120 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges {
});
}
+ private loadPresets(): void {
+ this.presets = this.localStorageService.getObject(PRESETS_KEY) ?? [];
+ }
+
+ private savePresets(): void {
+ this.localStorageService.setObject(PRESETS_KEY, this.presets);
+ }
+
+ public addOrUpdatePreset(): void {
+ const name = (this.presetName.value || '').trim();
+ const frequency = this.form.get('frequency')?.value;
+ const coreVoltage = this.form.get('coreVoltage')?.value;
+
+ if (!name) {
+ this.toastr.warning('Please enter a preset name.');
+ return;
+ }
+ if (typeof frequency !== 'number' || typeof coreVoltage !== 'number') {
+ this.toastr.warning('Set a valid Frequency and Core Voltage first.');
+ return;
+ }
+
+ if (this.editingIndex !== null) {
+ this.presets[this.editingIndex] = { name, frequency, coreVoltage };
+ } else {
+ if (this.presets.length >= MAX_PRESETS) {
+ this.toastr.warning(`You can save at most ${MAX_PRESETS} presets.`);
+ return;
+ }
+ this.presets.push({ name, frequency, coreVoltage });
+ }
+
+ this.savePresets();
+ this.presetName.reset('');
+ this.editingIndex = null;
+ }
+
+ public startEditPreset(index: number): void {
+ this.editingIndex = index;
+ this.presetName.setValue(this.presets[index].name);
+ }
+
+ public deletePreset(index: number): void {
+ this.presets.splice(index, 1);
+ this.savePresets();
+ if (this.editingIndex === index) {
+ this.editingIndex = null;
+ this.presetName.reset('');
+ }
+ }
+
+ public applyPreset(preset: Preset): void {
+ this.form.patchValue({ frequency: preset.frequency, coreVoltage: preset.coreVoltage });
+ this.form.controls['frequency'].markAsDirty();
+ this.form.controls['coreVoltage'].markAsDirty();
+
+ this.systemService.updateSystem(this.uri || '', {
+ frequency: preset.frequency,
+ coreVoltage: preset.coreVoltage
+ })
+ .pipe(this.loadingService.lockUIUntilComplete())
+ .subscribe({
+ next: () => this.toastr.success(`Applied preset "${preset.name}"`),
+ error: (err: HttpErrorResponse) => this.toastr.error(`Could not apply preset. ${err.message}`)
+ });
+ }
+
+ public exportPresets(): void {
+ const blob = new Blob([JSON.stringify(this.presets, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const anchor = document.createElement('a');
+ anchor.href = url;
+ anchor.download = 'axeos-presets.json';
+ anchor.click();
+ URL.revokeObjectURL(url);
+ }
+
+ public importPresets(event: Event): void {
+ const input = event.target as HTMLInputElement;
+ const file = input.files?.[0];
+ if (!file) {
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = () => {
+ try {
+ const parsed = JSON.parse(reader.result as string);
+ if (!Array.isArray(parsed)) {
+ throw new Error('Expected an array of presets');
+ }
+ const valid = parsed.filter((p: any) =>
+ p && typeof p.name === 'string' &&
+ typeof p.frequency === 'number' &&
+ typeof p.coreVoltage === 'number'
+ ).slice(0, MAX_PRESETS);
+
+ this.presets = valid.map((p: any) => ({
+ name: p.name,
+ frequency: p.frequency,
+ coreVoltage: p.coreVoltage
+ }));
+ this.savePresets();
+ this.editingIndex = null;
+ this.presetName.reset('');
+ this.toastr.success(`Imported ${this.presets.length} preset(s).`);
+ } catch (err: any) {
+ this.toastr.error(`Could not import presets. ${err.message}`);
+ }
+ };
+ reader.readAsText(file);
+ input.value = '';
+ }
+
disableOverheatMode() {
this.form.patchValue({ overheat_mode: 0 });
this.updateSystem();