Skip to content
Open
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 2.4.0

### Feature: per-model "Column Projection" toggle (KYTE-#342)

The model **Settings** tab gains a **Column Projection** switch. When enabled, the model's list and read endpoints honor the client's `X-Kyte-Fields` header and return only the requested columns instead of the full row — lighter, faster tables for models with large `text`/`longtext` columns. Off by default (full rows — unchanged behavior).

The switch reads and writes the `allow_projection` flag inside the model's `controller_config` JSON column (new in kyte-php v4.16.0). It **merges** rather than clobbers, so any other controller-config flags are preserved. Saves inline with the same auto-save + revert-on-failure UX as the Sensitive toggle (a partial PUT that omits `name`, so `DataModelController` skips the table-rename path).

**Requires kyte-php v4.16.0+** (the `controller_config` column + server-side projection opt-in). Against an older server the toggle persists its value but has no effect.

## 2.3.0

### Fix: password reset works in JWT mode (KYTE-#268)
Expand Down
11 changes: 11 additions & 0 deletions app/model/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,17 @@ <h6 class="card-title mb-2" data-i18n="ui.models.modal.field.sensitive">Sensitiv
<div id="model-sensitive-save-status" class="small mt-2 text-muted" style="min-height: 1.25em;"></div>
</div>
</div>
<div class="card mt-3" style="max-width: 720px;">
<div class="card-body">
<h6 class="card-title mb-2" data-i18n="ui.model_detail.projection_card_title">Column Projection</h6>
<p class="text-muted small mb-3" data-i18n="ui.model_detail.projection_description">When enabled, list and read endpoints honor the client's requested field list (the X-Kyte-Fields header) and return only those columns instead of the full row — lighter, faster tables for models with large columns. Leave off for controllers whose logic needs the full record.</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="model-setting-allow-projection">
<label class="form-check-label" for="model-setting-allow-projection" data-i18n="ui.model_detail.projection_label">Allow client-requested column projection</label>
</div>
<div id="model-projection-save-status" class="small mt-2 text-muted" style="min-height: 1.25em;"></div>
</div>
</div>
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@
"ui.model_detail.sensitive_saving": "Saving...",
"ui.model_detail.sensitive_saved": "Saved.",
"ui.model_detail.sensitive_save_failed": "Save failed. Reverting.",
"ui.model_detail.projection_card_title": "Column Projection",
"ui.model_detail.projection_description": "When enabled, list and read endpoints honor the client's requested field list (the X-Kyte-Fields header) and return only those columns instead of the full row — lighter, faster tables for models with large columns. Leave off for controllers whose logic needs the full record.",
"ui.model_detail.projection_label": "Allow client-requested column projection",
"ui.model_detail.projection_saving": "Saving...",
"ui.model_detail.projection_saved": "Saved.",
"ui.model_detail.projection_save_failed": "Save failed. Reverting.",

"ui.site_detail.back_to_sites": "Back to Sites",
"ui.site_detail.sidebar.content": "Content",
Expand Down
6 changes: 6 additions & 0 deletions assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@
"ui.model_detail.sensitive_saving": "Guardando...",
"ui.model_detail.sensitive_saved": "Guardado.",
"ui.model_detail.sensitive_save_failed": "Error al guardar. Revirtiendo.",
"ui.model_detail.projection_card_title": "Proyección de columnas",
"ui.model_detail.projection_description": "Cuando está habilitado, los endpoints de lista y lectura respetan la lista de campos solicitada por el cliente (el encabezado X-Kyte-Fields) y devuelven solo esas columnas en lugar de la fila completa, lo que hace que las tablas sean más ligeras y rápidas para modelos con columnas grandes. Déjelo desactivado para controladores cuya lógica necesita el registro completo.",
"ui.model_detail.projection_label": "Permitir proyección de columnas solicitada por el cliente",
"ui.model_detail.projection_saving": "Guardando...",
"ui.model_detail.projection_saved": "Guardado.",
"ui.model_detail.projection_save_failed": "Error al guardar. Revirtiendo.",

"ui.site_detail.back_to_sites": "Volver a Sitios",
"ui.site_detail.sidebar.content": "Contenido",
Expand Down
6 changes: 6 additions & 0 deletions assets/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@
"ui.model_detail.sensitive_saving": "保存中...",
"ui.model_detail.sensitive_saved": "保存しました。",
"ui.model_detail.sensitive_save_failed": "保存に失敗しました。元に戻します。",
"ui.model_detail.projection_card_title": "列プロジェクション",
"ui.model_detail.projection_description": "有効にすると、一覧および読み取りエンドポイントはクライアントが要求したフィールド一覧(X-Kyte-Fields ヘッダー)を尊重し、行全体ではなく該当する列のみを返します。大きな列を持つモデルの一覧が軽く高速になります。完全なレコードを必要とするコントローラーではオフのままにしてください。",
"ui.model_detail.projection_label": "クライアントが要求する列プロジェクションを許可",
"ui.model_detail.projection_saving": "保存中...",
"ui.model_detail.projection_saved": "保存しました。",
"ui.model_detail.projection_save_failed": "保存に失敗しました。元に戻します。",

"ui.site_detail.back_to_sites": "サイト一覧に戻る",
"ui.site_detail.sidebar.content": "コンテンツ",
Expand Down
6 changes: 6 additions & 0 deletions assets/i18n/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@
"ui.model_detail.sensitive_saving": "저장 중...",
"ui.model_detail.sensitive_saved": "저장됨.",
"ui.model_detail.sensitive_save_failed": "저장 실패. 되돌리는 중.",
"ui.model_detail.projection_card_title": "열 프로젝션",
"ui.model_detail.projection_description": "활성화하면 목록 및 읽기 엔드포인트가 클라이언트가 요청한 필드 목록(X-Kyte-Fields 헤더)을 따르며 전체 행 대신 해당 열만 반환합니다. 큰 열이 있는 모델의 테이블이 더 가볍고 빨라집니다. 전체 레코드가 필요한 컨트롤러에서는 꺼 두세요.",
"ui.model_detail.projection_label": "클라이언트 요청 열 프로젝션 허용",
"ui.model_detail.projection_saving": "저장 중...",
"ui.model_detail.projection_saved": "저장됨.",
"ui.model_detail.projection_save_failed": "저장 실패. 되돌리는 중.",

"ui.site_detail.back_to_sites": "사이트 목록으로 돌아가기",
"ui.site_detail.sidebar.content": "콘텐츠",
Expand Down
35 changes: 35 additions & 0 deletions assets/js/source/kyte-shipyard-model-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,41 @@ document.addEventListener('KyteInitialized', function(e) {
});
});

// Settings tab (KYTE-#342): allow_projection lives inside the
// controller_config JSON column. Parse it once, then toggle only
// the allow_projection key so any other controller_config flags
// are preserved, and PUT the merged JSON (partial update — no
// `name`, so DataModelController skips the table-rename path).
let controllerConfig = {};
try {
const raw = r.data[0].controller_config;
if (raw) {
const parsed = (typeof raw === "string") ? JSON.parse(raw) : raw;
if (parsed && typeof parsed === "object") controllerConfig = parsed;
}
} catch (e) {
controllerConfig = {};
}
$("#model-setting-allow-projection").prop("checked", controllerConfig.allow_projection === true);
$("#model-setting-allow-projection").off("change.projection").on("change.projection", function() {
const newVal = this.checked;
const $status = $("#model-projection-save-status");
const savingMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_saving')) || 'Saving...';
const savedMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_saved')) || 'Saved.';
const failMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_save_failed')) || 'Save failed. Reverting.';
$status.removeClass("text-success text-danger").addClass("text-muted").html('<i class="fas fa-spinner fa-spin me-1"></i>' + savingMsg);
// Merge into a copy so a failed save reverts cleanly.
const merged = Object.assign({}, controllerConfig, { allow_projection: newVal });
_ks.put("DataModel", "id", modelIdx, { controller_config: JSON.stringify(merged) }, null, [], function() {
controllerConfig = merged; // commit only on success
$status.removeClass("text-muted text-danger").addClass("text-success").html('<i class="fas fa-check me-1"></i>' + savedMsg);
setTimeout(() => $status.html(""), 2500);
}, function() {
$("#model-setting-allow-projection").prop("checked", !newVal);
$status.removeClass("text-muted text-success").addClass("text-danger").html('<i class="fas fa-exclamation-triangle me-1"></i>' + failMsg);
});
});

// Get i18n instance for translations
const i18n = window.kyteI18n;
const t = (key, fallback) => i18n ? i18n.t(key) : fallback;
Expand Down