# Ürün Modülü Müşteri Yönetimi

https://dev.wisecp.com/tr/urun-modulu-musteri-yonetimi

Bir ürün hizmetinde müşterinin ne göreceğine ve ne yapabileceğine beş isteğe bağlı üye karar verir. Bunlar yönetim sekmesi, göstergeler, kısayollar, aksiyonlar ve görünüm işaretidir.

## Genel Bakış

Ürün modülü müşteriye varsayılan olarak hiçbir şey açmaz. Aşağıdaki beş üye isteğe bağlıdır ve tipten bağımsızdır; doldurmak için çekirdekte değişiklik gerekmez.

**Özet** sekmesi `client_overview_data()` çıktısından kurulan kullanım halkalarını ve hesap satırlarını gösterir; **Yönetim** sekmesi sizin pano sayfanızı gösterir. İkisinde de gösterge olabilir, bu yüzden modül hangisini doldurduğunu bilmelidir.

- **has_client_management()**: Sekme kapısı.
- **client_overview_data()**: Özet sekmesinin göstergeleri ve hesap satırları.
- **client_quick_actions()**: Özetteki kısayol butonları.
- **client_callable_methods**: Müşterinin çalıştırabileceği adlar.
- **$client_area**: Yalnız panonun müşteri görünümünde true, yönetici görünümünde false.

## Ön Koşullar

- Çalışan bir ürün modülü: [Ürün Modülü Yazma](https://dev.wisecp.com/tr/urun-modulu-yazma).
- `special` ya da `software` tipinde, `active` ve bir modüle bağlı hizmet. Askıya alınmış dahil diğer durumlar başta reddedilir.
- Müşteri olarak test edin; yönetici görünümü işareti ayarlamaz.

## Yapı

| İstediğiniz | Modül ne yapar | Varsayılan |
| --- | --- | --- |
| Müşteride hiç ekran olmasın | Hiçbir şey | Varsayılan |
| Yönetim sekmesi olsun | `pages/dashboard.php` ekleyin ya da `page_dashboard()` tanımlayın | Sekme yok |
| Sekme olmasın, pano yalnız yöneticide | `has_client_management()` false döndürsün | Sekme çıkar |
| Özette kullanım göstergeleri | `client_overview_data()` doldurun | Boş, gösterge yok |
| Özette kısayollar | `client_quick_actions()` doldurun | Boş, kısayol yok |
| Çalıştırılabilir bir aksiyon | Adını `$client_callable_methods` içinde bildirin, `handle_{ad}()` yazın | Reddedilir |
| GET ile sunulan aksiyon (indirme) | Ayrıca `$client_readonly_methods` içinde sayın | Token ve POST |
| Bir pano bloğunu müşteriden gizleyin | Bloğu `$client_area` denetimiyle sarın | İkisine de görünür |

```bash
Yönetim sekmesine tıklandı
  └─ use_module_method'suz GET isteği   ->  ClientServices::get_management_details
       ├─ sahip + tip + etkin durum denetimi
       ├─ client_module_instance()        ->  $client_area = true, $area_link atanır
       ├─ ProductModule::service_management_page()  ->  get_page('dashboard')
       └─ satır içi script etiketleri ayrılır, tema köprüsünce ayrıca değerlendirilir

Genel Bakış sekmesi
  └─ hizmetler controller'ı, special ve software hizmetler için
       ├─ client_overview_data()   ->  kullanım halkaları + hesap satırları
       └─ client_quick_actions()   ->  kısayol butonları

Bir kısayol ya da pano butonu
  └─ POST use_module_method  ->  izin listesi  ->  handle_{name}()
```

## Adım Adım

### Yönetim Sekmesini Açın

Pano sayfası eklemek onaydır: kapı dosyaya bakar.

1. Modülünüzde `pages/dashboard.php` oluşturun; içeride `$module` canlı örnektir.
2. Pano yalnız yöneticiler içinse kapıyı ezin. Admin hizmet sayfası yine de gösterir.
3. Tema köprüsü satır içi script'leri HTML'den ayırır ve ayrıca değerlendirir.

### Özeti Doldurun

`client_overview_data()` üç liste döndürür. Henüz bir şey sağlanmamışken boş dizi döndürün; özet o zaman gösterge göstermez.

1. `gauges`: limitli her kaynak için bir kayıt; sıfır ya da altı sınırsız demektir.
2. `resources`: isteğe bağlı sayaçlar — kullanılan ve toplam biçimi ya da oran olmayan tek bir `value`.
3. `account`: kimlik satırları. Hizmet parolası sizin yerinize eklenir.

### Bir Aksiyon Açın

İki şey gerekir, tek başına hiçbiri yetmez: adın izin listesinde olması ve `handle_` ön ekli bir metot.

1. `public array $client_callable_methods = ['reset_usage'];` şeklinde, ön eksiz bildirin.
2. `public function handle_reset_usage(): array` yazın — argümansız; istekten ve hizmetten okuyun.
3. API istemcisini handler'ın içinde kurun: müşteri çağrısı için hiçbir şey hazırlanmaz.

### İki Kez Göstermekten Kaçının

Aynı dosya, özet sekmesi olmayan admin hizmet sayfasına da hizmet eder. Blokları silmek yerine `$client_area` arkasında gizleyin.

## Referans

### Beş Üye

```php
// ProductModule üzerinde bildirilir; ihtiyacınız olanı ezin.
public bool  $client_area            = false;   // yalnız müşteri basıcısı true yapar
public array $client_callable_methods = [];     // handle_ ön eki OLMADAN adlar
public array $client_readonly_methods = [];     // yukarıdakilerin alt kümesi, GET ile güvenli

public function has_client_management(): bool;
public function client_overview_data(): array;
public function client_quick_actions(int $limit = 8): array;

// Bildirilen bir çağrılabilir için işleyiciniz. Parametresiz; JSON yükünü döndürür.
public function handle_reset_usage(): array;
```

### client_overview_data() Biçimi

```php
return [
    'gauges' => [
        [
            'key'   => 'storage',
            'label' => $this->lang['storage-usage'],
            'icon'  => 'bi bi-hdd',
            'used'  => 4.5,
            'total' => 20,          // 0 ya da altı sınırsız demektir; netlik için -1 geçin
            'unit'  => 'GB',
        ],
    ],
    'resources' => [
        // Oran biçimi: göstergeyle aynı anahtarlar.
        ['key' => 'seats', 'label' => 'Seats', 'icon' => 'bi bi-people', 'used' => 3, 'total' => 10, 'unit' => ''],
        // Değer biçimi: limiti olmayan tek bir okuma.
        ['key' => 'region', 'label' => 'Region', 'icon' => 'bi bi-globe', 'value' => 'eu-west'],
    ],
    'account' => [
        ['key' => 'account_id', 'label' => 'Account ID', 'type' => 'text', 'copyable' => true, 'value' => 'ac_1042'],
        ['key' => 'username',   'label' => 'Username',   'type' => 'text', 'copyable' => true, 'value' => 'acme'],
        ['key' => 'status',     'label' => 'Status',     'type' => 'badge', 'badge_color' => 'success', 'value' => 'Active'],
        ['key' => 'console',    'label' => 'Console',    'type' => 'link',  'value' => 'https://panel.example.com'],
    ],
];
```

- **gauges: total**: Sıfır ya da altı sınırsızdır: gösterge halka şeridinden çıkıp sonsuz işaretiyle özellik listesine geçer.
- **gauges: yüzde yok**: Ham sayı gönderin; oranı ve renk kademesini tema hesaplar, hesaplanmış yüzde yoksayılır.
- **resources: sıfır toplam**: Göstergeden farklı olarak burada sıfır gerçek bir limittir. Yalnız negatif toplam sınırsızdır.
- **account: type**: `text`, `link`, `badge` ya da `password`. `copyable` kopyalama butonu ekler; bağlantı satırı tam genişlik alır.
- **account: badge_color**: `success`, `warning`, `danger` ya da varsayılan. İkon renkten gelir.
- **account: parola satırı**: Anahtarı `username` olan satırın ardına, öyle bir satır yoksa en sona eklenir.
- **her değer verildiği gibi gösterilir**: Tarihleri ve sayıları önce biçimlendirin: ham zaman damgası müşteriye öyle ulaşır.

### client_quick_actions() Biçimi

```php
public function client_quick_actions(int $limit = 8): array
{
    $actions = [
        // action => true: işleyiciyi yerinde, çağrılabilir izin listesi üzerinden koşturur.
        ['key' => 'reset_usage', 'label' => $this->lang['action-reset-usage'],
         'icon' => 'bi bi-arrow-counterclockwise', 'action' => true, 'method' => 'reset_usage'],

        // action => false: bunun yerine yönetim sekmesindeki bir sayfaya gider.
        ['key' => 'backups', 'label' => $this->lang['action-backups'],
         'icon' => 'bi bi-archive', 'action' => false, 'method' => 'backups'],
    ];

    // Limite uyun: kaç tanesinin sığacağına çağıran karar verir.
    return array_slice($actions, 0, $limit);
}
```

### Handler'ınız Çalışmadan Önce

"Aksiyonum hiçbir şey yapmıyor" bildirimlerinin çoğu bu retlerden biridir.

```php
// ClientServices::use_module_method, kararlara indirgenmiş hali.
$service = $this->owned_managed_service($uid);      // sahiplik, tip, modül ve aktif durum denetimi
$method  = (string) Filter::init("REQUEST/method", "route");

$module = $this->client_module_instance($service);  // $client_area ve $area_link değerlerini ayarlar

$handleMethod = 'handle_' . str_replace('-', '_', $method);
$isTool       = in_array($method, ['tool_action', 'tool_table', 'sso_panel_login'], true);

// İKİ koşul birden: izin listesinde bildirilmiş VE işleyici gerçekten var.
$isClientCallable = !$isTool
    && in_array($method, $module->client_callable_methods ?? [], true)
    && method_exists($module, $handleMethod);

if (!$isTool && !$isClientCallable)
    throw new \Exception(Language::gc("website/services/err-invalid"));

// Salt okunur çağrılabilirler jetonu ve aktif hizmet şartını atlar.
$isReadonly = $isClientCallable && in_array($method, $module->client_readonly_methods ?? [], true);
$mutates    = ($method === 'tool_action' || $isClientCallable) && !$isReadonly;

if ($mutates && !\Validation::verify_csrf_token((string) Filter::init("POST/token", "hclear"), "services"))
    throw new \Exception(Language::g("needs/csrf-failed"));

if ($mutates && ($service['status'] ?? '') !== 'active')
    throw new \Exception(Language::gc("website/services/err-invalid"));
```

- **sahiplik, tip ve modül**: Hizmet oturum açmış hesaba ait olmalı, hosting, server, special ya da software tipinde olmalı ve `none` dışında bir modül adı taşımalıdır. "Hizmet Detaylarını Kısıtla" ile işaretlenen hizmet de bulunamadı sayılır.
- **izin listesi isteğe bağlı değildir**: Bildirilmemiş bir handler, var olsa bile sessizce reddedilir; bu da bozuk bir buton gibi görünür. `handle_create` gibi metotları müşteriye kapalı tutan şey budur.
- **token ve canlı hizmet**: Sahiplik araması zaten `active` ister, dolayısıyla askıya alınmış hizmet okumada da reddedilir. Değişiklik yapan çağrı ayrıca geçerli bir token ister.
- **gate:service.client_tool**: Handler'ınızdan hemen önce çalışır. Boş olmayan bir dize döndüren dinleyici çağrıyı o mesajla veto eder.
- **action:service.client_tool_ran**: Sonrasında çalışır: hizmet, istenen metot, çözülen metot adı ve sonuç ile. Dönüş kullanılmaz.
- **filter:client.service_management_content**: Pano HTML'ini, satır içi script'ler ayrılmadan önce referansla filtreler.

## Örnek

Modül yarısı ve pano yarısı; `$client_area` kararı ikincisinde verilir.

```php
// handle_ ön eki olmadan bildirilir. Burada olmayan her şey reddedilir.
public array $client_callable_methods = ['reset_usage', 'download_report'];

// GET bağlantısı üzerinden sunulur, bu yüzden jeton ve POST şartından muaftır.
public array $client_readonly_methods = ['download_report'];

public function client_overview_data(): array
{
    $remote = $this->fetchRemoteStatus();
    if (!$remote) return [];                       // henüz bir şey sağlanmadı: gösterge yok

    $gauges = [];
    if (isset($remote['storage_limit']))
        $gauges[] = [
            'key'   => 'storage',
            'label' => $this->lang['storage-usage'],
            'icon'  => 'bi bi-hdd',
            'used'  => (float) ($remote['used_storage'] ?? 0),
            'total' => (float) $remote['storage_limit'] > 0 ? (float) $remote['storage_limit'] : -1,
            'unit'  => 'GB',
        ];

    $account = [];
    $username = (string) ($this->options['login']['username'] ?? '');

    // Controller parola satırını tam bunun ardına ekler.
    if ($username !== '')
        $account[] = ['key' => 'username', 'label' => $this->lang['username'], 'type' => 'text', 'copyable' => true, 'value' => $username];

    $status = (string) ($remote['status'] ?? '');
    if ($status !== '')
        $account[] = [
            'key'         => 'status',
            'label'       => $this->lang['account-status'],
            'type'        => 'badge',
            'badge_color' => $status === 'active' ? 'success' : 'secondary',
            'value'       => $this->lang['status-' . $status] ?? ucfirst($status),
        ];

    if (!$gauges && !$account) return [];

    return ['gauges' => $gauges, 'resources' => [], 'account' => $account];
}

public function handle_reset_usage(): array
{
    // Müşteri tetikler: API istemcisini sizin için hazırlayan hiçbir şey yok.
    $this->initApi();

    $accountId = $this->options['config']['id'] ?? '';
    if (!$accountId) throw new Exception($this->lang['err-not-provisioned']);

    $this->api->call('accounts/' . $accountId . '/usage/reset', [], 'POST');

    return ['status' => 'successful', 'message' => $this->lang['usage-reset-ok']];
}
```

```php
<?php
/** @var ProductModule $module */

// Yöneticide Özet sekmesi yok, bu yüzden bu blokları korumak zorunda. Müşteri aynı
// sayıları orada zaten görüyor; o yüzden burada silmek yerine gizleyin.
if (!($module->client_area ?? false)):
?>
    <div class="row">
        <!-- gösterge widget'ları ve hesap kartı -->
    </div>
<?php endif; ?>

<!-- Aksiyonlar iki görünümde de kalır: burası yönetim yüzeyidir. -->
<button type="button" class="btn btn-primary" id="acmeResetUsage">Reset Usage</button>

<script>
(function () {
    // Her aramayı null'a karşı koruyun: $client_area ile gizlenen blok kimliklerini de
    // götürür ve buradaki yakalanmamış hata bu düğmeyi değil tüm paneli düşürür.
    var btn = document.getElementById('acmeResetUsage');
    if (!btn) return;

    btn.addEventListener('click', function () {
        run_module_method('reset_usage');
    });
})();
</script>
```

## Tuzaklar

> **Korumasız bir eleman araması tüm paneli öldürür**
> 
> `$client_area` ile gizlenen blok, eleman kimliklerini de götürür. O kayıp düğüm üzerinde `addEventListener` çağıran satır içi script hata fırlatır. Script köprüsü de onunla düşer ve müşteri genel bir "yüklenemedi" mesajı alır. Her aramayı koruyun.

> **Kısayol kısayoldur, aksiyonun evi değildir**
> 
> Aksiyonu panoya koyun, kısayol da oraya işaret etsin.

> **Zamanlayıcı altında sayfa boş gelir**
> 
> Zamanlanmış görev bağlamında şablon çıktısı atlanır. Komut satırından bir sonda sıfır uzunlukta sayfa bildirir, gerçek istek panelin tamamını döndürür. Gerçek HTTP üzerinden ya da yönetici kimliğine bürünerek doğrulayın.

## İlgili Makaleler

- [Ürün Modülü Yazma](https://dev.wisecp.com/tr/urun-modulu-yazma)
- [Müşteri Paneli Köprüsü](https://dev.wisecp.com/tr/musteri-paneli-koprusu)
- [Sunucu Modülü Araçları](https://dev.wisecp.com/tr/sunucu-modulu-araclari)
- [Müşteri Paneli](https://dev.wisecp.com/tr/musteri-paneli)
- [Modülden Kanca Kaydetme](https://dev.wisecp.com/tr/modulden-kanca-kaydetme)
- [Güvenlik Pratikleri](https://dev.wisecp.com/tr/guvenlik-pratikleri)
